groove-dev 0.27.135 → 0.27.137
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/api.js +32 -0
- package/node_modules/@groove-dev/daemon/src/journalist.js +14 -12
- package/node_modules/@groove-dev/daemon/src/model-lab.js +50 -67
- package/node_modules/@groove-dev/daemon/src/rotator.js +49 -20
- package/node_modules/@groove-dev/gui/dist/assets/{index-D4JZyMWH.js → index-BrZHF7pK.js} +7 -7
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +5 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +8 -8
- package/node_modules/@groove-dev/gui/src/components/editor/file-tree.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +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/api.js +32 -0
- package/packages/daemon/src/journalist.js +14 -12
- package/packages/daemon/src/model-lab.js +50 -67
- package/packages/daemon/src/rotator.js +49 -20
- package/packages/gui/dist/assets/{index-D4JZyMWH.js → index-BrZHF7pK.js} +7 -7
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/agents/agent-feed.jsx +5 -1
- package/packages/gui/src/components/agents/agent-file-tree.jsx +1 -1
- package/packages/gui/src/components/editor/code-editor.jsx +8 -8
- package/packages/gui/src/components/editor/file-tree.jsx +1 -1
- package/packages/gui/src/components/lab/system-prompt-editor.jsx +3 -3
- package/packages/gui/src/components/layout/breadcrumb-bar.jsx +1 -1
|
@@ -124,6 +124,38 @@ export function createApi(app, daemon) {
|
|
|
124
124
|
res.json({ status: 'ok', uptime: process.uptime() });
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
// Debug: test fetch to llama-server from daemon runtime
|
|
128
|
+
app.get('/api/lab/debug-fetch', async (req, res) => {
|
|
129
|
+
const target = req.query.url || 'http://localhost:8081/v1/chat/completions';
|
|
130
|
+
const log = [];
|
|
131
|
+
try {
|
|
132
|
+
log.push(`fetch → ${target}`);
|
|
133
|
+
log.push(`node ${process.version}, electron ${process.versions.electron || 'N/A'}`);
|
|
134
|
+
const start = Date.now();
|
|
135
|
+
const r = await fetch(target, {
|
|
136
|
+
method: 'POST',
|
|
137
|
+
headers: { 'Content-Type': 'application/json' },
|
|
138
|
+
body: JSON.stringify({ model: 'Qwen3-0.6B-Q8_0.gguf', messages: [{ role: 'user', content: 'Say ok' }], stream: true, max_tokens: 10 }),
|
|
139
|
+
signal: AbortSignal.timeout(10000),
|
|
140
|
+
});
|
|
141
|
+
log.push(`status=${r.status} in ${Date.now() - start}ms`);
|
|
142
|
+
const reader = r.body.getReader();
|
|
143
|
+
let chunks = 0;
|
|
144
|
+
while (chunks < 5) {
|
|
145
|
+
const { done, value } = await reader.read();
|
|
146
|
+
if (done) break;
|
|
147
|
+
chunks++;
|
|
148
|
+
log.push(`chunk ${chunks}: ${new TextDecoder().decode(value).slice(0, 120)}`);
|
|
149
|
+
}
|
|
150
|
+
reader.cancel();
|
|
151
|
+
log.push(`total chunks read: ${chunks}`);
|
|
152
|
+
res.json({ ok: true, log });
|
|
153
|
+
} catch (err) {
|
|
154
|
+
log.push(`ERROR: ${err.message}`);
|
|
155
|
+
res.json({ ok: false, log, error: err.message });
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
127
159
|
// List all agents
|
|
128
160
|
app.get('/api/agents', (req, res) => {
|
|
129
161
|
res.json(daemon.registry.getAll());
|
|
@@ -853,9 +853,8 @@ export class Journalist {
|
|
|
853
853
|
const agentLog = filteredLogs[agent.id];
|
|
854
854
|
const entries = agentLog?.entries || [];
|
|
855
855
|
|
|
856
|
-
// Layer 7 memory: discoveries (pointer
|
|
857
|
-
const
|
|
858
|
-
const discoveryPointer = hasDiscoveries ? 'See .groove/memory/agent-discoveries.jsonl for error→fix pairs.' : '';
|
|
856
|
+
// Layer 7 memory: discoveries (inline, not pointer — agents lose context with pointers), constraints, specializations
|
|
857
|
+
const discoveries = this.daemon.memory?.getDiscoveriesMarkdown(agent.role, 10, 1500) || '';
|
|
859
858
|
const constraints = this.daemon.memory?.getConstraintsMarkdown(2000) || '';
|
|
860
859
|
const specialization = this.daemon.memory?.getSpecialization(agent.id);
|
|
861
860
|
const specLine = specialization?.avgQualityScore != null
|
|
@@ -872,7 +871,7 @@ export class Journalist {
|
|
|
872
871
|
const recentTools = entries
|
|
873
872
|
.filter((e) => e.type === 'tool' || e.type === 'error')
|
|
874
873
|
.slice(-5)
|
|
875
|
-
.map((e) => `- ${e.type === 'error' ? 'ERROR ' : ''}${e.tool}: ${(e.input || e.text || '').slice(0,
|
|
874
|
+
.map((e) => `- ${e.type === 'error' ? 'ERROR ' : ''}${e.tool}: ${(e.input || e.text || '').slice(0, 200)}`)
|
|
876
875
|
.join('\n');
|
|
877
876
|
|
|
878
877
|
// Try AI-synthesized session summary
|
|
@@ -909,7 +908,7 @@ export class Journalist {
|
|
|
909
908
|
const fallbackRecentTools = entries
|
|
910
909
|
.filter((e) => e.type === 'tool' || e.type === 'error')
|
|
911
910
|
.slice(-5)
|
|
912
|
-
.map((e) => `- ${e.type === 'error' ? 'ERROR ' : ''}${e.tool}: ${(e.input || '').slice(0,
|
|
911
|
+
.map((e) => `- ${e.type === 'error' ? 'ERROR ' : ''}${e.tool}: ${(e.input || '').slice(0, 200)}`)
|
|
913
912
|
.join('\n');
|
|
914
913
|
|
|
915
914
|
const fallbackParts = [];
|
|
@@ -923,8 +922,8 @@ export class Journalist {
|
|
|
923
922
|
// For quality_degradation rotations, drop user messages (already in session summary)
|
|
924
923
|
const includeUserMessages = options.reason !== 'quality_degradation';
|
|
925
924
|
|
|
926
|
-
// Cap Original Task to
|
|
927
|
-
const originalTask = agent.prompt ? agent.prompt.slice(0,
|
|
925
|
+
// Cap Original Task to 1000 chars — task descriptions for debugging can be long
|
|
926
|
+
const originalTask = agent.prompt ? agent.prompt.slice(0, 1000) + (agent.prompt.length > 1000 ? '…' : '') : '';
|
|
928
927
|
|
|
929
928
|
let brief = [
|
|
930
929
|
`# Handoff Brief — ${agent.name} (${agent.role})`,
|
|
@@ -934,10 +933,13 @@ export class Journalist {
|
|
|
934
933
|
`Rotation: ${options.reason || 'manual'}${options.qualityScore ? ` (quality: ${options.qualityScore}/100)` : ''} | Tokens: ${agent.tokensUsed}`,
|
|
935
934
|
specLine,
|
|
936
935
|
``,
|
|
937
|
-
|
|
936
|
+
// Priority order: session summary (contains unresolved errors) first,
|
|
937
|
+
// then constraints, then discoveries, then tools — so the most critical
|
|
938
|
+
// debugging context survives even if the brief hits the hard cap.
|
|
939
|
+
sessionSummary ? `## Session Summary\n\n${sessionSummary}\n` : '',
|
|
938
940
|
constraints ? `## Project Constraints (must follow)\n\n${constraints}\n` : '',
|
|
941
|
+
discoveries ? `## Known Issues & Fixes\n\n${discoveries}\n` : '',
|
|
939
942
|
recentTools ? `## Last 5 Tool Calls\n\n${recentTools}\n` : '',
|
|
940
|
-
sessionSummary ? `## Session Summary\n\n${sessionSummary}\n` : '',
|
|
941
943
|
includeUserMessages && conversationSummary ? `## Recent User Messages\n\n${conversationSummary}\n` : '',
|
|
942
944
|
recentChain ? `## Rotation History\n\n${recentChain}\n` : '',
|
|
943
945
|
originalTask ? `## Original Task\n\n${originalTask}\n` : '',
|
|
@@ -946,9 +948,9 @@ export class Journalist {
|
|
|
946
948
|
`Continue seamlessly — finish the work and deliver the output.`,
|
|
947
949
|
].filter(Boolean).join('\n');
|
|
948
950
|
|
|
949
|
-
// Hard cap:
|
|
950
|
-
if (brief.length >
|
|
951
|
-
brief = brief.slice(0,
|
|
951
|
+
// Hard cap: 8000 chars — enough for debugging context without overwhelming the new agent
|
|
952
|
+
if (brief.length > 8000) {
|
|
953
|
+
brief = brief.slice(0, 7950) + '\n\n[Brief truncated — see session logs for full context]';
|
|
952
954
|
}
|
|
953
955
|
|
|
954
956
|
return brief;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { resolve } from 'path';
|
|
5
5
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync } from 'fs';
|
|
6
6
|
import { randomUUID } from 'crypto';
|
|
7
|
+
import { Readable } from 'stream';
|
|
7
8
|
|
|
8
9
|
const RUNTIME_TYPES = ['ollama', 'vllm', 'llama-cpp', 'tgi', 'openai-compatible'];
|
|
9
10
|
const DEFAULT_OLLAMA_ENDPOINT = 'http://localhost:11434';
|
|
@@ -223,10 +224,9 @@ export class ModelLab {
|
|
|
223
224
|
...this._buildParameterBody(parameters || {}),
|
|
224
225
|
};
|
|
225
226
|
|
|
226
|
-
const endpoint =
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (rt.apiKey) headers['Authorization'] = `Bearer ${rt.apiKey}`;
|
|
227
|
+
const endpoint = rt.endpoint.replace('localhost', '127.0.0.1');
|
|
228
|
+
const reqHeaders = { 'Content-Type': 'application/json' };
|
|
229
|
+
if (rt.apiKey) reqHeaders['Authorization'] = `Bearer ${rt.apiKey}`;
|
|
230
230
|
|
|
231
231
|
const requestStart = Date.now();
|
|
232
232
|
let ttft = null;
|
|
@@ -236,85 +236,61 @@ export class ModelLab {
|
|
|
236
236
|
let generationStart = null;
|
|
237
237
|
let fullContent = '';
|
|
238
238
|
|
|
239
|
-
const resp = await fetch(endpoint
|
|
239
|
+
const resp = await fetch(`${endpoint}/v1/chat/completions`, {
|
|
240
240
|
method: 'POST',
|
|
241
|
-
headers,
|
|
241
|
+
headers: reqHeaders,
|
|
242
242
|
body: JSON.stringify(body),
|
|
243
243
|
signal: AbortSignal.timeout(300000),
|
|
244
244
|
});
|
|
245
245
|
|
|
246
246
|
if (!resp.ok) {
|
|
247
|
-
let
|
|
248
|
-
try {
|
|
249
|
-
throw new Error(
|
|
247
|
+
let errMsg = `HTTP ${resp.status}`;
|
|
248
|
+
try { const e = await resp.json(); errMsg = e.error?.message || errMsg; } catch { /* ignore */ }
|
|
249
|
+
throw new Error(errMsg);
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
const
|
|
253
|
-
const decoder = new TextDecoder();
|
|
252
|
+
const nodeStream = Readable.fromWeb(resp.body);
|
|
254
253
|
let buffer = '';
|
|
255
254
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
fullContent += delta.content;
|
|
288
|
-
completionTokens++;
|
|
289
|
-
onEvent({ type: 'token', content: delta.content });
|
|
290
|
-
}
|
|
291
|
-
if (chunk.usage) {
|
|
292
|
-
promptTokens = chunk.usage.prompt_tokens || 0;
|
|
293
|
-
totalTokens = chunk.usage.total_tokens || 0;
|
|
294
|
-
if (chunk.usage.completion_tokens) {
|
|
295
|
-
completionTokens = chunk.usage.completion_tokens;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
} catch { /* skip malformed chunk */ }
|
|
299
|
-
}
|
|
255
|
+
for await (const chunk of nodeStream) {
|
|
256
|
+
buffer += typeof chunk === 'string' ? chunk : chunk.toString('utf8');
|
|
257
|
+
const lines = buffer.split('\n');
|
|
258
|
+
buffer = lines.pop() || '';
|
|
259
|
+
|
|
260
|
+
for (const line of lines) {
|
|
261
|
+
const trimmed = line.trim();
|
|
262
|
+
if (!trimmed || !trimmed.startsWith('data: ')) continue;
|
|
263
|
+
const data = trimmed.slice(6);
|
|
264
|
+
if (data === '[DONE]') continue;
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const parsed = JSON.parse(data);
|
|
268
|
+
const delta = parsed.choices?.[0]?.delta;
|
|
269
|
+
if (delta?.reasoning_content) {
|
|
270
|
+
if (ttft === null) { ttft = Date.now() - requestStart; generationStart = Date.now(); }
|
|
271
|
+
completionTokens++;
|
|
272
|
+
onEvent({ type: 'reasoning', content: delta.reasoning_content });
|
|
273
|
+
}
|
|
274
|
+
if (delta?.content) {
|
|
275
|
+
if (ttft === null) { ttft = Date.now() - requestStart; generationStart = Date.now(); }
|
|
276
|
+
fullContent += delta.content;
|
|
277
|
+
completionTokens++;
|
|
278
|
+
onEvent({ type: 'token', content: delta.content });
|
|
279
|
+
}
|
|
280
|
+
if (parsed.usage) {
|
|
281
|
+
promptTokens = parsed.usage.prompt_tokens || 0;
|
|
282
|
+
totalTokens = parsed.usage.total_tokens || 0;
|
|
283
|
+
if (parsed.usage.completion_tokens) completionTokens = parsed.usage.completion_tokens;
|
|
284
|
+
}
|
|
285
|
+
} catch { /* skip malformed chunk */ }
|
|
300
286
|
}
|
|
301
|
-
} finally {
|
|
302
|
-
reader.releaseLock();
|
|
303
287
|
}
|
|
304
288
|
|
|
305
289
|
const generationTime = generationStart ? Date.now() - generationStart : Date.now() - requestStart;
|
|
306
290
|
const tokensPerSec = generationTime > 0 ? (completionTokens / (generationTime / 1000)) : 0;
|
|
307
291
|
|
|
308
|
-
let memoryUsage = null;
|
|
309
|
-
if (rt.type === 'ollama') {
|
|
310
|
-
memoryUsage = await this.getOllamaMemoryUsage(rt.endpoint);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
292
|
if (sessionId) {
|
|
314
|
-
this._appendToSession(sessionId, messages, {
|
|
315
|
-
role: 'assistant',
|
|
316
|
-
content: fullContent,
|
|
317
|
-
});
|
|
293
|
+
this._appendToSession(sessionId, messages, { role: 'assistant', content: fullContent });
|
|
318
294
|
}
|
|
319
295
|
|
|
320
296
|
onEvent({
|
|
@@ -326,9 +302,16 @@ export class ModelLab {
|
|
|
326
302
|
promptTokens,
|
|
327
303
|
completionTokens,
|
|
328
304
|
generationTime,
|
|
329
|
-
memoryUsage,
|
|
305
|
+
memoryUsage: null,
|
|
330
306
|
},
|
|
331
307
|
});
|
|
308
|
+
|
|
309
|
+
if (rt.type === 'ollama') {
|
|
310
|
+
try {
|
|
311
|
+
const mem = await this.getOllamaMemoryUsage(rt.endpoint);
|
|
312
|
+
if (mem) onEvent({ type: 'memory', usage: mem });
|
|
313
|
+
} catch { /* ignore */ }
|
|
314
|
+
}
|
|
332
315
|
}
|
|
333
316
|
|
|
334
317
|
_buildParameterBody(params) {
|
|
@@ -147,11 +147,19 @@ export class Rotator extends EventEmitter {
|
|
|
147
147
|
const signals = signalsEarly;
|
|
148
148
|
let score = this.daemon.adaptive.scoreSession(signals);
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
150
|
+
// Age penalties: only for providers that don't manage their own context.
|
|
151
|
+
// Claude Code handles context internally via compaction — long sessions
|
|
152
|
+
// are normal and productive. Penalizing age causes premature rotation
|
|
153
|
+
// that destroys active debugging context and creates restart loops.
|
|
154
|
+
const providerForAge = getProvider(agent.provider);
|
|
155
|
+
const selfManagesForAge = providerForAge?.constructor?.managesOwnContext ?? false;
|
|
156
|
+
if (!selfManagesForAge) {
|
|
157
|
+
if (ageSec > 1800) score -= 5;
|
|
158
|
+
if (ageSec > 3600) score -= 10;
|
|
159
|
+
if (ageSec > 7200) score -= 15;
|
|
160
|
+
if (ageSec > 14400) score -= 20;
|
|
161
|
+
if (ageSec > 28800) score -= 25;
|
|
162
|
+
}
|
|
155
163
|
|
|
156
164
|
score = Math.max(0, Math.min(100, score));
|
|
157
165
|
|
|
@@ -240,16 +248,23 @@ export class Rotator extends EventEmitter {
|
|
|
240
248
|
}
|
|
241
249
|
}
|
|
242
250
|
|
|
243
|
-
// --- Change 4: Truncation-triggered
|
|
244
|
-
|
|
251
|
+
// --- Change 4: Truncation-triggered rotation ---
|
|
252
|
+
// Self-managing providers need more consecutive truncations — single
|
|
253
|
+
// truncations can be transient API issues, not session degradation.
|
|
254
|
+
const truncationThreshold = selfManagesContext ? 4 : 2;
|
|
255
|
+
if (agent.consecutiveTruncations >= truncationThreshold) {
|
|
245
256
|
console.log(` Rotator: ${agent.name} consecutiveTruncations=${agent.consecutiveTruncations} — FORCE rotating (incomplete_response)`);
|
|
246
257
|
await this.rotate(agent.id, { reason: 'incomplete_response', qualityScore: 0 });
|
|
247
258
|
continue;
|
|
248
259
|
}
|
|
249
260
|
|
|
250
|
-
// --- Change 3: Compaction-aware rotation
|
|
261
|
+
// --- Change 3: Compaction-aware rotation ---
|
|
262
|
+
// Only for non-self-managing providers. Claude Code compacts internally
|
|
263
|
+
// as part of normal operation — it's healthy, not degradation. Counting
|
|
264
|
+
// compactions toward a ceiling causes premature rotation that destroys
|
|
265
|
+
// active debugging sessions and creates restart loops.
|
|
251
266
|
const compactions = this.compactionCounts.get(agent.id) || 0;
|
|
252
|
-
if (compactions >= 5) {
|
|
267
|
+
if (!selfManagesContext && compactions >= 5) {
|
|
253
268
|
console.log(` Rotator: ${agent.name} compactions=${compactions} — FORCE rotating (compaction_ceiling)`);
|
|
254
269
|
await this.rotate(agent.id, { reason: 'compaction_ceiling' });
|
|
255
270
|
continue;
|
|
@@ -259,18 +274,30 @@ export class Rotator extends EventEmitter {
|
|
|
259
274
|
// agents don't persist producing bad output for 8-10 minutes
|
|
260
275
|
if (this._isOnCooldown(agent.id, QUALITY_COOLDOWN_MS)) continue;
|
|
261
276
|
|
|
262
|
-
// Effective quality threshold
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
277
|
+
// Effective quality threshold depends on provider type.
|
|
278
|
+
// Self-managing providers (Claude Code): threshold = 15. Only rotate on
|
|
279
|
+
// truly catastrophic degradation. Normal debugging naturally produces
|
|
280
|
+
// errors, retries, and bash repetitions — the scoring model treats these
|
|
281
|
+
// as degradation but they're expected behavior during investigation.
|
|
282
|
+
// A threshold of 40 (the default) kills debugging sessions at ~8 minutes.
|
|
283
|
+
let effectiveQualityThreshold;
|
|
284
|
+
if (selfManagesContext) {
|
|
285
|
+
effectiveQualityThreshold = 15;
|
|
286
|
+
} else {
|
|
287
|
+
effectiveQualityThreshold = QUALITY_THRESHOLD;
|
|
288
|
+
if (compactions >= 3) effectiveQualityThreshold = 55;
|
|
289
|
+
if (agent.truncationSuspected || agent.cacheResetDetected) {
|
|
290
|
+
effectiveQualityThreshold = Math.max(effectiveQualityThreshold, 55);
|
|
291
|
+
}
|
|
266
292
|
}
|
|
267
293
|
|
|
268
|
-
// All providers: quality-based rotation — detects degradation before tokens are wasted
|
|
269
294
|
const quality = this.scoreLiveSession(agent);
|
|
270
295
|
if (quality.hasEnoughData && quality.score < effectiveQualityThreshold) {
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
|
|
296
|
+
// For self-managing providers, effectiveQualityThreshold IS the severe
|
|
297
|
+
// threshold (15) — any score below it is catastrophic, rotate immediately.
|
|
298
|
+
// For others, severe = < 25, moderate = 25-40/55.
|
|
299
|
+
const severeThreshold = selfManagesContext ? effectiveQualityThreshold : 25;
|
|
300
|
+
if (quality.score < severeThreshold) {
|
|
274
301
|
console.log(` Rotator: ${agent.name} quality=${quality.score} — FORCE rotating (severe degradation)`);
|
|
275
302
|
await this.rotate(agent.id, {
|
|
276
303
|
reason: 'quality_degradation',
|
|
@@ -279,8 +306,10 @@ export class Rotator extends EventEmitter {
|
|
|
279
306
|
});
|
|
280
307
|
continue;
|
|
281
308
|
}
|
|
282
|
-
// Moderate degradation (25-40):
|
|
283
|
-
|
|
309
|
+
// Moderate degradation (25-40): only for non-self-managing providers.
|
|
310
|
+
// Claude Code sessions should never be killed for "moderate" quality —
|
|
311
|
+
// errors during debugging are expected, not degradation.
|
|
312
|
+
if (!selfManagesContext && this._idleMs(agent) > 10_000) {
|
|
284
313
|
console.log(` Rotator: ${agent.name} quality=${quality.score} — rotating (quality)`);
|
|
285
314
|
await this.rotate(agent.id, {
|
|
286
315
|
reason: 'quality_degradation',
|
|
@@ -343,7 +372,7 @@ export class Rotator extends EventEmitter {
|
|
|
343
372
|
reason: options.reason || 'manual',
|
|
344
373
|
oldTokens: agent.tokensUsed,
|
|
345
374
|
contextUsage: agent.contextUsage,
|
|
346
|
-
brief: brief.slice(0,
|
|
375
|
+
brief: brief.slice(0, 6000),
|
|
347
376
|
}, agent.workingDir, agent.teamId);
|
|
348
377
|
}
|
|
349
378
|
|