groove-dev 0.27.191 → 0.27.193

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.191",
3
+ "version": "0.27.193",
4
4
  "description": "GROOVE CLI — manage AI coding agents from your terminal",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/daemon",
3
- "version": "0.27.191",
3
+ "version": "0.27.193",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -131,7 +131,13 @@ export class InnerChat {
131
131
  };
132
132
  thread.turns.push(turn);
133
133
 
134
- const wrapped = this._wrap(thread, fromAgent, toAgent, message, kind);
134
+ // Only replay earlier turns when the recipient has LOST them. A running
135
+ // agent already has this thread's history in its live session — re-injecting
136
+ // it every exchange duplicates content and balloons context. A stopped agent
137
+ // will be resumed/rotated (fresh context), so it needs the recap.
138
+ const recipientHasContext =
139
+ this.daemon.processes.isRunning(toAgent.id) || this.daemon.processes.hasAgentLoop(toAgent.id);
140
+ const wrapped = this._wrap(thread, fromAgent, toAgent, message, kind, !recipientHasContext);
135
141
 
136
142
  let result;
137
143
  try {
@@ -322,11 +328,11 @@ export class InnerChat {
322
328
  // turns to follow a continuing conversation. The closing instruction differs
323
329
  // by kind: an ask blocks the sender (answer now), a tell does not (answer
324
330
  // when you reach a good stopping point).
325
- _wrap(thread, fromAgent, toAgent, message, kind = 'ask') {
331
+ _wrap(thread, fromAgent, toAgent, message, kind = 'ask', includePrior = true) {
326
332
  const header = kind === 'tell'
327
333
  ? `[InnerChat — ${fromAgent.name} (${fromAgent.role}) sent you a message]`
328
334
  : `[InnerChat — ${fromAgent.name} (${fromAgent.role}) is asking you a question]`;
329
- const prior = thread.turns.slice(0, -1).slice(-CONTEXT_TURNS);
335
+ const prior = includePrior ? thread.turns.slice(0, -1).slice(-CONTEXT_TURNS) : [];
330
336
  const lines = [header, ''];
331
337
 
332
338
  if (prior.length) {
@@ -2687,7 +2687,7 @@ After fixing all issues, run tests (npm test) and build (npm run build) to verif
2687
2687
  locks.release(agentId);
2688
2688
 
2689
2689
  // Build resume command
2690
- const { command: rawCommand, args, env } = provider.buildResumeCommand(sessionId, message, config.model, { fast: config.fast });
2690
+ const { command: rawCommand, args, env, stdin: stdinData } = provider.buildResumeCommand(sessionId, message, config.model, { fast: config.fast });
2691
2691
  const command = resolveProviderCommand(config.provider || 'claude-code') || rawCommand;
2692
2692
 
2693
2693
  // Set up log capture
@@ -2752,10 +2752,17 @@ After fixing all issues, run tests (npm test) and build (npm run build) to verif
2752
2752
  const proc = cpSpawn(command, args, {
2753
2753
  cwd: resumeCwd,
2754
2754
  env: resumeEnv,
2755
- stdio: ['ignore', 'pipe', 'pipe'],
2755
+ stdio: [stdinData ? 'pipe' : 'ignore', 'pipe', 'pipe'],
2756
2756
  detached: false,
2757
2757
  });
2758
2758
 
2759
+ // The resume message goes via stdin (not argv) to avoid ARG_MAX/E2BIG on
2760
+ // large messages — mirror the spawn path.
2761
+ if (stdinData && proc.stdin) {
2762
+ proc.stdin.write(stdinData);
2763
+ proc.stdin.end();
2764
+ }
2765
+
2759
2766
  proc.on('error', (err) => {
2760
2767
  if (!logStream.destroyed) logStream.write(`[${new Date().toISOString()}] Resume spawn error: ${err.message}\n`);
2761
2768
  if (!logStream.destroyed) logStream.end();
@@ -111,16 +111,19 @@ export class ClaudeCodeProvider extends Provider {
111
111
  args.push('--fast');
112
112
  }
113
113
 
114
- // Pass the initial prompt as positional arg (includes GROOVE context)
114
+ // Pass the initial prompt via STDIN, not as a positional argument. A
115
+ // long-running agent's handoff brief accumulates over days and easily
116
+ // exceeds the OS argv limit — on Linux a single arg is capped at 128KB
117
+ // (MAX_ARG_STRLEN) regardless of ARG_MAX — which surfaced as `spawn E2BIG`
118
+ // on rotation. stdin has no such limit. (interactive stream-json mode reads
119
+ // its prompt from stdin, same as headless -p.)
115
120
  const fullPrompt = this.buildFullPrompt(agent);
116
- if (fullPrompt) {
117
- args.push(fullPrompt);
118
- }
119
121
 
120
122
  return {
121
123
  command: 'claude',
122
124
  args,
123
125
  env: {},
126
+ stdin: fullPrompt || undefined,
124
127
  };
125
128
  }
126
129
 
@@ -132,8 +135,8 @@ export class ClaudeCodeProvider extends Provider {
132
135
  if (knockSettings) args.push('--settings', knockSettings);
133
136
  if (model) args.push('--model', model);
134
137
  if (options.fast) args.push('--fast');
135
- if (prompt) args.push(prompt);
136
- return { command: 'claude', args, env: {} };
138
+ // Via stdin, not argv — the resume message can be large (see buildSpawnCommand).
139
+ return { command: 'claude', args, env: {}, stdin: prompt || undefined };
137
140
  }
138
141
 
139
142
  /**
@@ -232,7 +232,9 @@ describe('InnerChat', () => {
232
232
 
233
233
  // ── Conversation context ──────────────────────────────────
234
234
 
235
- it('replays prior turns so neither side re-explains', async () => {
235
+ it('does NOT replay prior turns to a running agent (it already has them)', async () => {
236
+ // a2 stays running throughout — re-injecting history it already holds would
237
+ // balloon its context.
236
238
  const p1 = innerchat.ask('a1', 'a2', 'What shape?');
237
239
  await tick(); innerchat.onAgentOutput('a2', result('REST'));
238
240
  await p1;
@@ -240,12 +242,32 @@ describe('InnerChat', () => {
240
242
  const p2 = innerchat.ask('a1', 'a2', 'Versioned?');
241
243
  await tick();
242
244
  const msg = daemon.sent.at(-1).msg;
245
+ assert.doesNotMatch(msg, /Earlier in this conversation/, 'no redundant recap to a live agent');
246
+ assert.match(msg, /Versioned\?/);
247
+
248
+ innerchat.onAgentOutput('a2', result('v2'));
249
+ await p2;
250
+ });
251
+
252
+ it('DOES replay prior turns when the recipient was stopped (lost its context)', async () => {
253
+ const p1 = innerchat.ask('a1', 'a2', 'What shape?');
254
+ await tick(); innerchat.onAgentOutput('a2', result('REST'));
255
+ await p1;
256
+
257
+ // a2 ends its turn — a resume/rotate gives it a fresh context, so it needs
258
+ // the recap to follow the thread.
259
+ daemon.processes._loops.delete('a2');
260
+ daemon.processes._running.delete('a2');
261
+
262
+ const p2 = innerchat.ask('a1', 'a2', 'Versioned?');
263
+ await tick();
264
+ const newId = daemon.resumes.at(-1).newId;
265
+ const msg = daemon.resumes.at(-1).msg;
243
266
  assert.match(msg, /Earlier in this conversation/);
244
267
  assert.match(msg, /fullstack-1: What shape\?/);
245
268
  assert.match(msg, /fullstack-14: REST/);
246
- assert.match(msg, /Versioned\?/);
247
269
 
248
- innerchat.onAgentOutput('a2', result('v2'));
270
+ innerchat.onAgentOutput(newId, result('v2'));
249
271
  await p2;
250
272
  });
251
273
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/gui",
3
- "version": "0.27.191",
3
+ "version": "0.27.193",
4
4
  "description": "GROOVE GUI — visual agent control plane",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.27.191",
3
+ "version": "0.27.193",
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)",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.191",
3
+ "version": "0.27.193",
4
4
  "description": "GROOVE CLI — manage AI coding agents from your terminal",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/daemon",
3
- "version": "0.27.191",
3
+ "version": "0.27.193",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -131,7 +131,13 @@ export class InnerChat {
131
131
  };
132
132
  thread.turns.push(turn);
133
133
 
134
- const wrapped = this._wrap(thread, fromAgent, toAgent, message, kind);
134
+ // Only replay earlier turns when the recipient has LOST them. A running
135
+ // agent already has this thread's history in its live session — re-injecting
136
+ // it every exchange duplicates content and balloons context. A stopped agent
137
+ // will be resumed/rotated (fresh context), so it needs the recap.
138
+ const recipientHasContext =
139
+ this.daemon.processes.isRunning(toAgent.id) || this.daemon.processes.hasAgentLoop(toAgent.id);
140
+ const wrapped = this._wrap(thread, fromAgent, toAgent, message, kind, !recipientHasContext);
135
141
 
136
142
  let result;
137
143
  try {
@@ -322,11 +328,11 @@ export class InnerChat {
322
328
  // turns to follow a continuing conversation. The closing instruction differs
323
329
  // by kind: an ask blocks the sender (answer now), a tell does not (answer
324
330
  // when you reach a good stopping point).
325
- _wrap(thread, fromAgent, toAgent, message, kind = 'ask') {
331
+ _wrap(thread, fromAgent, toAgent, message, kind = 'ask', includePrior = true) {
326
332
  const header = kind === 'tell'
327
333
  ? `[InnerChat — ${fromAgent.name} (${fromAgent.role}) sent you a message]`
328
334
  : `[InnerChat — ${fromAgent.name} (${fromAgent.role}) is asking you a question]`;
329
- const prior = thread.turns.slice(0, -1).slice(-CONTEXT_TURNS);
335
+ const prior = includePrior ? thread.turns.slice(0, -1).slice(-CONTEXT_TURNS) : [];
330
336
  const lines = [header, ''];
331
337
 
332
338
  if (prior.length) {
@@ -2687,7 +2687,7 @@ After fixing all issues, run tests (npm test) and build (npm run build) to verif
2687
2687
  locks.release(agentId);
2688
2688
 
2689
2689
  // Build resume command
2690
- const { command: rawCommand, args, env } = provider.buildResumeCommand(sessionId, message, config.model, { fast: config.fast });
2690
+ const { command: rawCommand, args, env, stdin: stdinData } = provider.buildResumeCommand(sessionId, message, config.model, { fast: config.fast });
2691
2691
  const command = resolveProviderCommand(config.provider || 'claude-code') || rawCommand;
2692
2692
 
2693
2693
  // Set up log capture
@@ -2752,10 +2752,17 @@ After fixing all issues, run tests (npm test) and build (npm run build) to verif
2752
2752
  const proc = cpSpawn(command, args, {
2753
2753
  cwd: resumeCwd,
2754
2754
  env: resumeEnv,
2755
- stdio: ['ignore', 'pipe', 'pipe'],
2755
+ stdio: [stdinData ? 'pipe' : 'ignore', 'pipe', 'pipe'],
2756
2756
  detached: false,
2757
2757
  });
2758
2758
 
2759
+ // The resume message goes via stdin (not argv) to avoid ARG_MAX/E2BIG on
2760
+ // large messages — mirror the spawn path.
2761
+ if (stdinData && proc.stdin) {
2762
+ proc.stdin.write(stdinData);
2763
+ proc.stdin.end();
2764
+ }
2765
+
2759
2766
  proc.on('error', (err) => {
2760
2767
  if (!logStream.destroyed) logStream.write(`[${new Date().toISOString()}] Resume spawn error: ${err.message}\n`);
2761
2768
  if (!logStream.destroyed) logStream.end();
@@ -111,16 +111,19 @@ export class ClaudeCodeProvider extends Provider {
111
111
  args.push('--fast');
112
112
  }
113
113
 
114
- // Pass the initial prompt as positional arg (includes GROOVE context)
114
+ // Pass the initial prompt via STDIN, not as a positional argument. A
115
+ // long-running agent's handoff brief accumulates over days and easily
116
+ // exceeds the OS argv limit — on Linux a single arg is capped at 128KB
117
+ // (MAX_ARG_STRLEN) regardless of ARG_MAX — which surfaced as `spawn E2BIG`
118
+ // on rotation. stdin has no such limit. (interactive stream-json mode reads
119
+ // its prompt from stdin, same as headless -p.)
115
120
  const fullPrompt = this.buildFullPrompt(agent);
116
- if (fullPrompt) {
117
- args.push(fullPrompt);
118
- }
119
121
 
120
122
  return {
121
123
  command: 'claude',
122
124
  args,
123
125
  env: {},
126
+ stdin: fullPrompt || undefined,
124
127
  };
125
128
  }
126
129
 
@@ -132,8 +135,8 @@ export class ClaudeCodeProvider extends Provider {
132
135
  if (knockSettings) args.push('--settings', knockSettings);
133
136
  if (model) args.push('--model', model);
134
137
  if (options.fast) args.push('--fast');
135
- if (prompt) args.push(prompt);
136
- return { command: 'claude', args, env: {} };
138
+ // Via stdin, not argv — the resume message can be large (see buildSpawnCommand).
139
+ return { command: 'claude', args, env: {}, stdin: prompt || undefined };
137
140
  }
138
141
 
139
142
  /**
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/gui",
3
- "version": "0.27.191",
3
+ "version": "0.27.193",
4
4
  "description": "GROOVE GUI — visual agent control plane",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",