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.
- 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/innerchat.js +9 -3
- package/node_modules/@groove-dev/daemon/src/process.js +9 -2
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +9 -6
- package/node_modules/@groove-dev/daemon/test/innerchat.test.js +25 -3
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/innerchat.js +9 -3
- package/packages/daemon/src/process.js +9 -2
- package/packages/daemon/src/providers/claude-code.js +9 -6
- package/packages/gui/package.json +1 -1
|
@@ -131,7 +131,13 @@ export class InnerChat {
|
|
|
131
131
|
};
|
|
132
132
|
thread.turns.push(turn);
|
|
133
133
|
|
|
134
|
-
|
|
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
|
|
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
|
-
|
|
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('
|
|
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(
|
|
270
|
+
innerchat.onAgentOutput(newId, result('v2'));
|
|
249
271
|
await p2;
|
|
250
272
|
});
|
|
251
273
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.27.
|
|
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)",
|
|
@@ -131,7 +131,13 @@ export class InnerChat {
|
|
|
131
131
|
};
|
|
132
132
|
thread.turns.push(turn);
|
|
133
133
|
|
|
134
|
-
|
|
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
|
|
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
|
-
|
|
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
|
/**
|