codeep 1.2.62 → 1.2.63
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/dist/renderer/App.d.ts +2 -0
- package/dist/renderer/App.js +12 -2
- package/dist/renderer/agentExecution.js +2 -0
- package/dist/utils/agent.js +118 -5
- package/package.json +1 -1
package/dist/renderer/App.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ export declare class App {
|
|
|
41
41
|
private spinnerInterval;
|
|
42
42
|
private isAgentRunning;
|
|
43
43
|
private agentIteration;
|
|
44
|
+
private agentMaxIterations;
|
|
44
45
|
private agentActions;
|
|
45
46
|
private agentThinking;
|
|
46
47
|
private pasteInfo;
|
|
@@ -173,6 +174,7 @@ export declare class App {
|
|
|
173
174
|
target: string;
|
|
174
175
|
result: string;
|
|
175
176
|
}): void;
|
|
177
|
+
setAgentMaxIterations(max: number): void;
|
|
176
178
|
/**
|
|
177
179
|
* Set agent thinking text
|
|
178
180
|
*/
|
package/dist/renderer/App.js
CHANGED
|
@@ -99,6 +99,7 @@ export class App {
|
|
|
99
99
|
// Agent progress state
|
|
100
100
|
isAgentRunning = false;
|
|
101
101
|
agentIteration = 0;
|
|
102
|
+
agentMaxIterations = 0;
|
|
102
103
|
agentActions = [];
|
|
103
104
|
agentThinking = '';
|
|
104
105
|
// Paste detection state
|
|
@@ -361,6 +362,7 @@ export class App {
|
|
|
361
362
|
this.isAgentRunning = running;
|
|
362
363
|
if (running) {
|
|
363
364
|
this.agentIteration = 0;
|
|
365
|
+
this.agentMaxIterations = 0;
|
|
364
366
|
this.agentActions = [];
|
|
365
367
|
this.agentThinking = '';
|
|
366
368
|
this.isLoading = false; // Clear loading state when agent takes over
|
|
@@ -382,6 +384,9 @@ export class App {
|
|
|
382
384
|
}
|
|
383
385
|
this.render();
|
|
384
386
|
}
|
|
387
|
+
setAgentMaxIterations(max) {
|
|
388
|
+
this.agentMaxIterations = max;
|
|
389
|
+
}
|
|
385
390
|
/**
|
|
386
391
|
* Set agent thinking text
|
|
387
392
|
*/
|
|
@@ -1445,7 +1450,10 @@ export class App {
|
|
|
1445
1450
|
// Agent running state - show special prompt
|
|
1446
1451
|
if (this.isAgentRunning) {
|
|
1447
1452
|
const spinner = SPINNER_FRAMES[this.spinnerFrame];
|
|
1448
|
-
const
|
|
1453
|
+
const stepLabel = this.agentMaxIterations > 0
|
|
1454
|
+
? `step ${this.agentIteration}/${this.agentMaxIterations}`
|
|
1455
|
+
: `step ${this.agentIteration}`;
|
|
1456
|
+
const agentText = `${spinner} Agent working... ${stepLabel} | ${this.agentActions.length} actions (Esc to stop)`;
|
|
1449
1457
|
this.screen.writeLine(y, agentText, PRIMARY_COLOR);
|
|
1450
1458
|
this.screen.showCursor(false);
|
|
1451
1459
|
return;
|
|
@@ -1932,7 +1940,9 @@ export class App {
|
|
|
1932
1940
|
x += txt.length + 1;
|
|
1933
1941
|
}
|
|
1934
1942
|
// Step info on the right
|
|
1935
|
-
const stepText =
|
|
1943
|
+
const stepText = this.agentMaxIterations > 0
|
|
1944
|
+
? `step ${this.agentIteration}/${this.agentMaxIterations}`
|
|
1945
|
+
: `step ${this.agentIteration}`;
|
|
1936
1946
|
this.screen.write(width - stepText.length - 1, y, stepText, fg.gray);
|
|
1937
1947
|
y++;
|
|
1938
1948
|
// Bottom border with help
|
|
@@ -135,6 +135,8 @@ export async function executeAgentTask(task, dryRun, ctx) {
|
|
|
135
135
|
try {
|
|
136
136
|
const fileContext = ctx.formatAddedFilesContext();
|
|
137
137
|
const enrichedTask = fileContext ? fileContext + task : task;
|
|
138
|
+
// Show N/M progress in status bar
|
|
139
|
+
app.setAgentMaxIterations(config.get('agentMaxIterations'));
|
|
138
140
|
const result = await runAgent(enrichedTask, context, {
|
|
139
141
|
dryRun,
|
|
140
142
|
chatHistory: app.getChatHistory(),
|
package/dist/utils/agent.js
CHANGED
|
@@ -38,6 +38,56 @@ import { startSession, endSession, undoLastAction, undoAllActions, getCurrentSes
|
|
|
38
38
|
import { runAllVerifications, formatErrorsForAgent, hasVerificationErrors, getVerificationSummary } from './verify.js';
|
|
39
39
|
import { gatherSmartContext, formatSmartContext, extractTargetFile } from './smartContext.js';
|
|
40
40
|
import { planTasks, formatTaskPlan } from './taskPlanner.js';
|
|
41
|
+
// ─── Tool result truncation ───────────────────────────────────────────────────
|
|
42
|
+
const TOOL_RESULT_MAX_CHARS = 8_000; // ~2K tokens per tool result
|
|
43
|
+
function truncateToolResult(output, toolName) {
|
|
44
|
+
if (output.length <= TOOL_RESULT_MAX_CHARS)
|
|
45
|
+
return output;
|
|
46
|
+
const kept = output.slice(0, TOOL_RESULT_MAX_CHARS);
|
|
47
|
+
const truncated = output.length - TOOL_RESULT_MAX_CHARS;
|
|
48
|
+
return `${kept}\n[... ${truncated} chars truncated — use search_code or read specific sections if you need more]`;
|
|
49
|
+
}
|
|
50
|
+
// ─── Context window compression ───────────────────────────────────────────────
|
|
51
|
+
const CONTEXT_COMPRESS_THRESHOLD = 80_000; // ~20K tokens, safe for all providers
|
|
52
|
+
const RECENT_MESSAGES_TO_KEEP = 6; // Always preserve the last N messages verbatim
|
|
53
|
+
/**
|
|
54
|
+
* Compress old messages when the conversation grows too large.
|
|
55
|
+
* Keeps the first message (original task) and the last RECENT_MESSAGES_TO_KEEP
|
|
56
|
+
* messages intact. Everything in between is replaced with a compact summary
|
|
57
|
+
* built from the actions log — no extra API call needed.
|
|
58
|
+
*/
|
|
59
|
+
function compressMessages(messages, actions) {
|
|
60
|
+
const totalChars = messages.reduce((sum, m) => sum + m.content.length, 0);
|
|
61
|
+
if (totalChars < CONTEXT_COMPRESS_THRESHOLD)
|
|
62
|
+
return messages;
|
|
63
|
+
// Need at least first + recent block to be worth compressing
|
|
64
|
+
if (messages.length <= RECENT_MESSAGES_TO_KEEP + 1)
|
|
65
|
+
return messages;
|
|
66
|
+
const firstMessage = messages[0];
|
|
67
|
+
const recentMessages = messages.slice(-RECENT_MESSAGES_TO_KEEP);
|
|
68
|
+
// Build summary from action log
|
|
69
|
+
const fileWrites = actions.filter(a => a.type === 'write' || a.type === 'edit');
|
|
70
|
+
const fileDeletes = actions.filter(a => a.type === 'delete');
|
|
71
|
+
const commands = actions.filter(a => a.type === 'command');
|
|
72
|
+
const reads = actions.filter(a => a.type === 'read');
|
|
73
|
+
const summaryLines = ['[Context compressed — summary of work so far]'];
|
|
74
|
+
if (fileWrites.length > 0) {
|
|
75
|
+
summaryLines.push(`Files written/edited (${fileWrites.length}): ${fileWrites.map(a => a.target).join(', ')}`);
|
|
76
|
+
}
|
|
77
|
+
if (fileDeletes.length > 0) {
|
|
78
|
+
summaryLines.push(`Files deleted: ${fileDeletes.map(a => a.target).join(', ')}`);
|
|
79
|
+
}
|
|
80
|
+
if (commands.length > 0) {
|
|
81
|
+
summaryLines.push(`Commands run: ${commands.map(a => a.target).join(', ')}`);
|
|
82
|
+
}
|
|
83
|
+
if (reads.length > 0) {
|
|
84
|
+
summaryLines.push(`Files read (${reads.length}): ${reads.slice(-10).map(a => a.target).join(', ')}`);
|
|
85
|
+
}
|
|
86
|
+
summaryLines.push('[End of summary — continuing from current state]');
|
|
87
|
+
const summaryMessage = { role: 'user', content: summaryLines.join('\n') };
|
|
88
|
+
debug(`Context compressed: ${totalChars} chars → keeping first + summary + last ${RECENT_MESSAGES_TO_KEEP} messages`);
|
|
89
|
+
return [firstMessage, summaryMessage, ...recentMessages];
|
|
90
|
+
}
|
|
41
91
|
const DEFAULT_OPTIONS = {
|
|
42
92
|
maxIterations: 100, // Increased for large tasks
|
|
43
93
|
maxDuration: 20 * 60 * 1000, // 20 minutes
|
|
@@ -129,6 +179,11 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
129
179
|
const maxTimeoutRetries = 3;
|
|
130
180
|
const maxConsecutiveTimeouts = 9; // Allow more consecutive timeouts before giving up
|
|
131
181
|
const baseTimeout = config.get('agentApiTimeout');
|
|
182
|
+
// Infinite loop detection: track last write hash per file path
|
|
183
|
+
const lastWriteHashByPath = new Map();
|
|
184
|
+
let duplicateWriteCount = 0;
|
|
185
|
+
// Duplicate read cache: path → truncated output (avoid re-sending large file content)
|
|
186
|
+
const readCache = new Map();
|
|
132
187
|
try {
|
|
133
188
|
while (iteration < opts.maxIterations) {
|
|
134
189
|
// Check timeout
|
|
@@ -156,6 +211,13 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
156
211
|
}
|
|
157
212
|
iteration++;
|
|
158
213
|
opts.onIteration?.(iteration, `Iteration ${iteration}/${opts.maxIterations}`);
|
|
214
|
+
// Compress messages if context window is getting full
|
|
215
|
+
const compressed = compressMessages(messages, actions);
|
|
216
|
+
if (compressed !== messages) {
|
|
217
|
+
messages.length = 0;
|
|
218
|
+
messages.push(...compressed);
|
|
219
|
+
opts.onIteration?.(iteration, `Context compressed (${compressed.length} messages kept)`);
|
|
220
|
+
}
|
|
159
221
|
debug(`Starting iteration ${iteration}/${opts.maxIterations}, actions: ${actions.length}`);
|
|
160
222
|
// Calculate dynamic timeout based on task complexity
|
|
161
223
|
const dynamicTimeout = calculateDynamicTimeout(prompt, iteration, baseTimeout);
|
|
@@ -294,13 +356,57 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
294
356
|
// Log action
|
|
295
357
|
const actionLog = createActionLog(toolCall, toolResult);
|
|
296
358
|
actions.push(actionLog);
|
|
297
|
-
//
|
|
298
|
-
if (
|
|
299
|
-
|
|
359
|
+
// ── Infinite loop detection for write/edit ──────────────────────────
|
|
360
|
+
if (toolCall.tool === 'write_file' || toolCall.tool === 'edit_file') {
|
|
361
|
+
const filePath = toolCall.parameters.path || '';
|
|
362
|
+
const contentKey = JSON.stringify(toolCall.parameters).slice(0, 500);
|
|
363
|
+
const prevHash = lastWriteHashByPath.get(filePath);
|
|
364
|
+
if (prevHash === contentKey) {
|
|
365
|
+
duplicateWriteCount++;
|
|
366
|
+
if (duplicateWriteCount >= 2) {
|
|
367
|
+
toolResults.push(`[WARNING] You have written the same content to \`${filePath}\` ${duplicateWriteCount + 1} times in a row. You are stuck in a loop. Stop and think differently — read the file to check its current state, then try a completely different approach.`);
|
|
368
|
+
duplicateWriteCount = 0;
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
toolResults.push(`Tool ${toolCall.tool} succeeded (note: same content as previous write to this file):\n${toolResult.output}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
duplicateWriteCount = 0;
|
|
376
|
+
lastWriteHashByPath.set(filePath, contentKey);
|
|
377
|
+
if (toolResult.success) {
|
|
378
|
+
toolResults.push(`Tool ${toolCall.tool} succeeded:\n${toolResult.output}`);
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
toolResults.push(`Tool ${toolCall.tool} failed:\n${toolResult.error || 'Unknown error'}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// ── Duplicate read cache ────────────────────────────────────────────
|
|
385
|
+
}
|
|
386
|
+
else if (toolCall.tool === 'read_file' && toolResult.success) {
|
|
387
|
+
const filePath = toolCall.parameters.path || '';
|
|
388
|
+
if (readCache.has(filePath)) {
|
|
389
|
+
toolResults.push(`Tool read_file succeeded (cached — file unchanged since last read):\n${readCache.get(filePath)}`);
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
const truncated = truncateToolResult(toolResult.output, toolCall.tool);
|
|
393
|
+
readCache.set(filePath, truncated);
|
|
394
|
+
toolResults.push(`Tool read_file succeeded:\n${truncated}`);
|
|
395
|
+
}
|
|
396
|
+
// ── General truncation for other tools ─────────────────────────────
|
|
397
|
+
}
|
|
398
|
+
else if (toolResult.success) {
|
|
399
|
+
const truncated = truncateToolResult(toolResult.output, toolCall.tool);
|
|
400
|
+
toolResults.push(`Tool ${toolCall.tool} succeeded:\n${truncated}`);
|
|
300
401
|
}
|
|
301
402
|
else {
|
|
302
403
|
toolResults.push(`Tool ${toolCall.tool} failed:\n${toolResult.error || 'Unknown error'}`);
|
|
303
404
|
}
|
|
405
|
+
// Invalidate read cache when a file is written/edited
|
|
406
|
+
if ((toolCall.tool === 'write_file' || toolCall.tool === 'edit_file') && toolResult.success) {
|
|
407
|
+
const filePath = toolCall.parameters.path || '';
|
|
408
|
+
readCache.delete(filePath);
|
|
409
|
+
}
|
|
304
410
|
}
|
|
305
411
|
// Add tool results to messages
|
|
306
412
|
messages.push({
|
|
@@ -308,13 +414,20 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
308
414
|
content: `Tool results:\n\n${toolResults.join('\n\n')}\n\nContinue with the task. If this subtask is complete, provide a summary without tool calls.`,
|
|
309
415
|
});
|
|
310
416
|
}
|
|
311
|
-
// Check if we hit max iterations
|
|
417
|
+
// Check if we hit max iterations — build partial summary from actions log
|
|
312
418
|
if (iteration >= opts.maxIterations && !finalResponse) {
|
|
419
|
+
const filesDone = actions.filter(a => a.type === 'write' || a.type === 'edit').map(a => a.target);
|
|
420
|
+
const partialLines = [`Agent reached the iteration limit (${opts.maxIterations} steps).`];
|
|
421
|
+
if (filesDone.length > 0) {
|
|
422
|
+
partialLines.push(`\n**Partial progress — files written/edited:**`);
|
|
423
|
+
[...new Set(filesDone)].forEach(f => partialLines.push(` ✓ \`${f}\``));
|
|
424
|
+
partialLines.push(`\nThe task may be incomplete. You can continue by running the agent again.`);
|
|
425
|
+
}
|
|
313
426
|
result = {
|
|
314
427
|
success: false,
|
|
315
428
|
iterations: iteration,
|
|
316
429
|
actions,
|
|
317
|
-
finalResponse: '
|
|
430
|
+
finalResponse: partialLines.join('\n'),
|
|
318
431
|
error: `Exceeded maximum of ${opts.maxIterations} iterations`,
|
|
319
432
|
};
|
|
320
433
|
return result;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeep",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.63",
|
|
4
4
|
"description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|