Skip to content

Commit 42006f7

Browse files
author
Brendan Gray
committed
v1.8.26: Major Context Management System Overhaul - File progress tracking in ConversationSummarizer - Auto-detection of incremental tasks (large line/function counts) - Auto-todo creation for large tasks (autoCreateLargeTaskTodos) - Continuation abort now triggers rotation instead of stopping - Strengthened post-rotation prompts with DO NOT REFUSE directive
1 parent 21cafe4 commit 42006f7

3 files changed

Lines changed: 255 additions & 15 deletions

File tree

main/agenticChat.js

Lines changed: 150 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,71 @@ const DATA_GATHER_TOOLS = new Set([
6363
]);
6464
const DATA_WRITE_TOOLS = new Set(['write_file', 'edit_file']);
6565

66+
/**
67+
* Detect if a user message describes a large/incremental task and auto-create todos.
68+
* This helps models stay on track during context rotations.
69+
*/
70+
function autoCreateLargeTaskTodos(message, mcpToolServer) {
71+
if (!message || !mcpToolServer) return null;
72+
const lower = message.toLowerCase();
73+
74+
// Pattern: Large line count requests (500+ lines)
75+
const lineMatch = lower.match(/(\d{3,})\s*[-]?\s*lines?/);
76+
if (lineMatch && parseInt(lineMatch[1], 10) >= 500) {
77+
const targetLines = parseInt(lineMatch[1], 10);
78+
const chunks = Math.ceil(targetLines / 500);
79+
const items = [];
80+
items.push({ text: `Create initial file structure`, status: 'pending' });
81+
for (let i = 1; i <= Math.min(chunks, 8); i++) {
82+
const start = (i - 1) * 500 + 1;
83+
const end = Math.min(i * 500, targetLines);
84+
items.push({ text: `Write lines ${start}-${end}`, status: 'pending' });
85+
}
86+
if (chunks > 8) {
87+
items.push({ text: `Continue writing remaining ${targetLines - 4000} lines`, status: 'pending' });
88+
}
89+
items.push({ text: `Review and finalize output`, status: 'pending' });
90+
return mcpToolServer._writeTodos({ items });
91+
}
92+
93+
// Pattern: Multiple functions/items (20+)
94+
const funcMatch = lower.match(/(\d{2,})\s*(?:utility\s*)?functions?/);
95+
if (funcMatch && parseInt(funcMatch[1], 10) >= 20) {
96+
const targetFuncs = parseInt(funcMatch[1], 10);
97+
const chunks = Math.ceil(targetFuncs / 10);
98+
const items = [];
99+
items.push({ text: `Create file and initial structure`, status: 'pending' });
100+
for (let i = 1; i <= Math.min(chunks, 6); i++) {
101+
const start = (i - 1) * 10 + 1;
102+
const end = Math.min(i * 10, targetFuncs);
103+
items.push({ text: `Implement functions ${start}-${end}`, status: 'pending' });
104+
}
105+
if (chunks > 6) {
106+
items.push({ text: `Continue implementing remaining ${targetFuncs - 60} functions`, status: 'pending' });
107+
}
108+
items.push({ text: `Verify all functions are exported`, status: 'pending' });
109+
return mcpToolServer._writeTodos({ items });
110+
}
111+
112+
// Pattern: Multiple files (5+)
113+
const fileMatch = lower.match(/(\d+)\s*(?:different\s*)?files?/);
114+
if (fileMatch && parseInt(fileMatch[1], 10) >= 5) {
115+
const targetFiles = parseInt(fileMatch[1], 10);
116+
const items = [];
117+
items.push({ text: `Plan file structure`, status: 'pending' });
118+
for (let i = 1; i <= Math.min(targetFiles, 10); i++) {
119+
items.push({ text: `Create file ${i} of ${targetFiles}`, status: 'pending' });
120+
}
121+
if (targetFiles > 10) {
122+
items.push({ text: `Continue creating remaining ${targetFiles - 10} files`, status: 'pending' });
123+
}
124+
items.push({ text: `Verify all files created`, status: 'pending' });
125+
return mcpToolServer._writeTodos({ items });
126+
}
127+
128+
return null;
129+
}
130+
66131
function register(ctx) {
67132
const {
68133
llmEngine, cloudLLM, mcpToolServer, playwrightBrowser, browserManager,
@@ -294,6 +359,19 @@ function register(ctx) {
294359
const summarizer = new ctx.ConversationSummarizer();
295360
summarizer.setGoal(message);
296361

362+
// Auto-create todos for large/incremental tasks (helps model track progress across rotations)
363+
const autoTodoResult = autoCreateLargeTaskTodos(message, mcpToolServer);
364+
if (autoTodoResult?.success) {
365+
console.log(`[Cloud] Auto-created ${autoTodoResult.created?.length || 0} todos for incremental task`);
366+
if (mainWindow && !mainWindow.isDestroyed()) {
367+
mainWindow.webContents.send('mcp-tool-results', [{
368+
tool: 'write_todos',
369+
params: { items: autoTodoResult.created },
370+
result: autoTodoResult,
371+
}]);
372+
}
373+
}
374+
297375
while (iteration < MAX_CLOUD_ITERATIONS) {
298376
if (isStale()) {
299377
if (mainWindow) mainWindow.webContents.send('llm-token', '\n*[Interrupted]*\n');
@@ -755,6 +833,19 @@ function register(ctx) {
755833
const summarizer = new ConversationSummarizer();
756834
summarizer.setGoal(message);
757835

836+
// Auto-create todos for large/incremental tasks (helps model track progress across rotations)
837+
const autoTodoResult = autoCreateLargeTaskTodos(message, mcpToolServer);
838+
if (autoTodoResult?.success) {
839+
console.log(`[AI Chat] Auto-created ${autoTodoResult.created?.length || 0} todos for incremental task`);
840+
if (mainWindow && !mainWindow.isDestroyed()) {
841+
mainWindow.webContents.send('mcp-tool-results', [{
842+
tool: 'write_todos',
843+
params: { items: autoTodoResult.created },
844+
result: autoTodoResult,
845+
}]);
846+
}
847+
}
848+
758849
// Transactional rollback
759850
let rollbackRetries = 0;
760851
const maxRollbackRetries = modelTier.retryBudget;
@@ -1039,9 +1130,23 @@ function register(ctx) {
10391130
}
10401131

10411132
const partial = fullResponseText.trim().length > 0 ? fullResponseText.substring(Math.max(0, fullResponseText.length - 1500)) : '';
1133+
1134+
// Build strong continuation prompt with incremental progress tracking
1135+
const incrementalHint = summarizer.incrementalTask
1136+
? `\n**INCREMENTAL TASK: ${summarizer.incrementalTask.current}/${summarizer.incrementalTask.target} ${summarizer.incrementalTask.type} completed.**`
1137+
: '';
1138+
const fileProgressHint = Object.keys(summarizer.fileProgress).length > 0
1139+
? `\n**FILES IN PROGRESS:** ${Object.entries(summarizer.fileProgress).map(([f, p]) => `${f} (${p.writtenLines} lines)`).join(', ')}`
1140+
: '';
1141+
10421142
const hint = partial
1043-
? `\n\nYou were generating and context was rotated. Here is the end of what you wrote:\n---\n${partial}\n---\nContinue from where you left off.`
1044-
: `\nContext was rotated. The user request is: ${message.substring(0, 300)}`;
1143+
? `\n\n## CONTINUE FROM HERE\n---\n${partial}\n---` +
1144+
incrementalHint + fileProgressHint +
1145+
`\n\n**CRITICAL: DO NOT REFUSE. DO NOT SAY "I cannot continue."**` +
1146+
`\nUse append_to_file to add more content. Call a tool NOW to make progress.`
1147+
: `\nContext was rotated. The user request is: ${message.substring(0, 300)}` +
1148+
incrementalHint + fileProgressHint +
1149+
`\n\n**Continue the task using tools. Do not refuse.**`;
10451150

10461151
currentPrompt = {
10471152
systemContext: buildStaticPrompt(),
@@ -1337,15 +1442,14 @@ function register(ctx) {
13371442
// Increased from 50K to allow large file generation — context rotation will handle memory
13381443
const MAX_CONTINUATION_CHARS = 500000; // 500K chars (~125K lines of code)
13391444
let _contAbortReason = '';
1340-
let _contShouldRotate = false;
1445+
// ALWAYS attempt rotation on any abort reason if rotations available
1446+
let _contShouldRotate = contextRotations < MAX_CONTEXT_ROTATIONS;
13411447
if (fullResponseText.length > MAX_CONTINUATION_CHARS) {
1342-
// Instead of hard abort, trigger context rotation to continue with fresh buffer
1343-
_contShouldRotate = true;
13441448
_contAbortReason = `total output exceeds ${MAX_CONTINUATION_CHARS} chars — rotating context`;
13451449
} else if (_contLowProgressCount >= 3) {
1346-
_contAbortReason = 'no forward progress';
1450+
_contAbortReason = 'no forward progress — rotating context to recover';
13471451
} else if (_contRepeatCount >= 2) {
1348-
_contAbortReason = 'repeated/near-identical content';
1452+
_contAbortReason = 'repeated/near-identical content — rotating context';
13491453
}
13501454

13511455
// Forward-progress scoring: after 5 passes, if avg chars per pass varies <10% from first pass, abort
@@ -1364,21 +1468,41 @@ function register(ctx) {
13641468
_contRepeatCount = 0;
13651469
_contCharSizes = [];
13661470

1367-
// If rotation was requested (char limit hit but task continues), trigger context rotation
1471+
// ALWAYS attempt rotation to recover and continue the task
13681472
if (_contShouldRotate && contextRotations < MAX_CONTEXT_ROTATIONS) {
13691473
console.log(`[AI Chat] Large-output rotation triggered (${contextRotations + 1}/${MAX_CONTEXT_ROTATIONS})`);
13701474
contextRotations++;
13711475
try {
1372-
// Store what we have so far and rotate
1476+
// Generate full summary with file progress tracking
1477+
summarizer.markRotation();
1478+
const convSummary = summarizer.generateSummary({
1479+
maxTokens: Math.min(Math.floor(totalCtx * 0.25), 3000),
1480+
activeTodos: mcpToolServer?._todos || [],
1481+
});
1482+
1483+
// Store partial output for context
13731484
const partialOutput = fullResponseText.slice(-Math.min(fullResponseText.length, 2000));
13741485
await llmEngine.resetSession(true);
13751486
await ensureLlmChat(llmEngine, getNodeLlamaCppPath);
13761487
if (mainWindow && !mainWindow.isDestroyed()) {
13771488
mainWindow.webContents.send('llm-thinking-token', '\n[Context rotated for large output]\n');
13781489
}
1490+
1491+
// Build strong continuation prompt with summary, progress, and directive
1492+
const incrementalHint = summarizer.incrementalTask
1493+
? `\n**INCREMENTAL TASK: ${summarizer.incrementalTask.current}/${summarizer.incrementalTask.target} ${summarizer.incrementalTask.type} completed.**`
1494+
: '';
1495+
const fileProgressHint = Object.keys(summarizer.fileProgress).length > 0
1496+
? `\n**FILES IN PROGRESS:** ${Object.entries(summarizer.fileProgress).map(([f, p]) => `${f} (${p.writtenLines} lines)`).join(', ')}`
1497+
: '';
1498+
13791499
currentPrompt = {
13801500
systemContext: buildStaticPrompt(),
1381-
userMessage: buildDynamicContext() + `\n\nYou were generating a large output and context was rotated. Continue from where you left off:\n---\n${partialOutput}\n---\n`,
1501+
userMessage: buildDynamicContext() + '\n\n' + convSummary +
1502+
`\n\n## CONTINUE FROM HERE\n---\n${partialOutput}\n---` +
1503+
incrementalHint + fileProgressHint +
1504+
`\n\n**CRITICAL: DO NOT REFUSE. DO NOT SAY "I cannot continue."**` +
1505+
`\nUse append_to_file to add more content. Call a tool NOW to make progress.`,
13821506
};
13831507
sessionJustRotated = true;
13841508
continue;
@@ -1887,12 +2011,24 @@ function preGenerationContextCheck(opts) {
18872011
} catch (_) {}
18882012
const summary = summarizer.generateQuickSummary(mcpToolServer?._todos);
18892013

2014+
// Build incremental progress hints
2015+
const incrementalHint = summarizer.incrementalTask
2016+
? `\n**INCREMENTAL TASK: ${summarizer.incrementalTask.current}/${summarizer.incrementalTask.target} ${summarizer.incrementalTask.type} completed.**`
2017+
: '';
2018+
const fileProgressHint = Object.keys(summarizer.fileProgress || {}).length > 0
2019+
? `\n**FILES IN PROGRESS:** ${Object.entries(summarizer.fileProgress).map(([f, p]) => `${f} (${p.writtenLines} lines)`).join(', ')}`
2020+
: '';
2021+
const noRefuseDirective = incrementalHint || fileProgressHint
2022+
? `\n\n**CRITICAL: DO NOT REFUSE. Use append_to_file to continue. Call a tool NOW.**`
2023+
: '';
2024+
18902025
if (isContinuationRotation) {
18912026
return {
18922027
shouldContinue: true,
18932028
prompt: {
18942029
systemContext: buildStaticPrompt(),
1895-
userMessage: buildDynamicContext(Math.floor(maxPromptTokens * 0.10)) + '\n' + message,
2030+
userMessage: buildDynamicContext(Math.floor(maxPromptTokens * 0.10)) + '\n' + message +
2031+
incrementalHint + fileProgressHint + noRefuseDirective,
18962032
},
18972033
rotated: true,
18982034
summary,
@@ -1904,7 +2040,9 @@ function preGenerationContextCheck(opts) {
19042040
shouldContinue: true,
19052041
prompt: {
19062042
systemContext: buildStaticPrompt(),
1907-
userMessage: buildDynamicContext() + '\n' + summary + `\nContext rotated. Current request: ${message.substring(0, 300)}`,
2043+
userMessage: buildDynamicContext() + '\n' + summary +
2044+
`\nContext rotated. Current request: ${message.substring(0, 300)}` +
2045+
incrementalHint + fileProgressHint + noRefuseDirective,
19082046
},
19092047
rotated: true,
19102048
summary,

main/conversationSummarizer.js

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,54 @@ class ConversationSummarizer {
2222
this.totalToolCalls = 0;
2323
this._warmTierResults = []; // Recent tool results carried across rotation
2424
this._previousSummaries = []; // Compacted summaries from prior rotations
25+
this.fileProgress = {}; // {filePath: {writtenLines, writtenChars, lastWriteIteration}}
26+
this.incrementalTask = null; // {type, target, current} — tracks progress on large tasks
2527
}
2628

2729
// ─── Goal ───
2830
setGoal(message) {
2931
if (!message) return;
3032
this.originalGoal = message.slice(0, 2000);
33+
34+
// Detect incremental task patterns in the goal
35+
this._detectIncrementalTask(message);
36+
}
37+
38+
// ─── Incremental Task Detection ───
39+
_detectIncrementalTask(message) {
40+
if (!message) return;
41+
const lower = message.toLowerCase();
42+
43+
// Pattern: "N lines" or "N-line"
44+
const lineMatch = lower.match(/(\d{3,})\s*[-]?\s*lines?/);
45+
if (lineMatch) {
46+
this.incrementalTask = { type: 'lines', target: parseInt(lineMatch[1], 10), current: 0 };
47+
return;
48+
}
49+
50+
// Pattern: "N functions"
51+
const funcMatch = lower.match(/(\d{2,})\s*(?:utility\s*)?functions?/);
52+
if (funcMatch) {
53+
this.incrementalTask = { type: 'functions', target: parseInt(funcMatch[1], 10), current: 0 };
54+
return;
55+
}
56+
57+
// Pattern: "N items/elements/components"
58+
const itemMatch = lower.match(/(\d{2,})\s*(?:items?|elements?|components?|methods?|classes?)/);
59+
if (itemMatch) {
60+
this.incrementalTask = { type: 'items', target: parseInt(itemMatch[1], 10), current: 0 };
61+
return;
62+
}
63+
}
64+
65+
setIncrementalTask(type, target, current = 0) {
66+
this.incrementalTask = { type, target, current };
67+
}
68+
69+
updateIncrementalProgress(amount) {
70+
if (this.incrementalTask) {
71+
this.incrementalTask.current += amount;
72+
}
3173
}
3274

3375
// ─── Tool Call Recording ───
@@ -97,6 +139,42 @@ class ConversationSummarizer {
97139
}
98140
this.currentState.lastAction = toolName;
99141
this.currentState.lastActionTime = Date.now();
142+
143+
// Track file write progress for incremental tasks
144+
if ((toolName === 'write_file' || toolName === 'append_to_file') && result?.success !== false) {
145+
const filePath = params?.filePath || params?.path;
146+
const content = params?.content || '';
147+
const lines = content.split('\n').length;
148+
const chars = content.length;
149+
if (filePath) {
150+
if (!this.fileProgress[filePath]) {
151+
this.fileProgress[filePath] = { writtenLines: 0, writtenChars: 0, writes: 0 };
152+
}
153+
if (toolName === 'write_file') {
154+
// write_file replaces — reset count
155+
this.fileProgress[filePath] = { writtenLines: lines, writtenChars: chars, writes: 1 };
156+
} else {
157+
// append_to_file adds
158+
this.fileProgress[filePath].writtenLines += lines;
159+
this.fileProgress[filePath].writtenChars += chars;
160+
this.fileProgress[filePath].writes++;
161+
}
162+
163+
// Update incremental task progress if tracking lines
164+
if (this.incrementalTask && this.incrementalTask.type === 'lines') {
165+
// Sum all written lines across all files
166+
this.incrementalTask.current = Object.values(this.fileProgress).reduce((sum, fp) => sum + fp.writtenLines, 0);
167+
}
168+
169+
// Estimate function count from content for function-based tasks
170+
if (this.incrementalTask && this.incrementalTask.type === 'functions') {
171+
const funcMatches = content.match(/function\s+\w+|(?:const|let|var)\s+\w+\s*=\s*(?:async\s*)?\(/g);
172+
if (funcMatches) {
173+
this.incrementalTask.current += funcMatches.length;
174+
}
175+
}
176+
}
177+
}
100178
}
101179

102180
_extractFindings(toolName, result) {
@@ -275,6 +353,22 @@ class ConversationSummarizer {
275353
sections.push(`## RECENT RESULTS (pre-rotation)\n${this._warmTierResults.join('\n')}`);
276354
}
277355

356+
// 5d. File progress for incremental tasks
357+
const fileProgressKeys = Object.keys(this.fileProgress);
358+
if (fileProgressKeys.length > 0) {
359+
const progressLines = fileProgressKeys.map(fp => {
360+
const p = this.fileProgress[fp];
361+
return `- ${fp}: ${p.writtenLines} lines (${p.writtenChars} chars) written in ${p.writes} operation(s)`;
362+
});
363+
sections.push(`## FILE PROGRESS\n${progressLines.join('\n')}\n**Use append_to_file to continue adding content to existing files.**`);
364+
}
365+
366+
// 5e. Incremental task tracking
367+
if (this.incrementalTask) {
368+
const pct = this.incrementalTask.target > 0 ? Math.round((this.incrementalTask.current / this.incrementalTask.target) * 100) : 0;
369+
sections.push(`## INCREMENTAL TASK PROGRESS\n- Type: ${this.incrementalTask.type}\n- Target: ${this.incrementalTask.target}\n- Current: ${this.incrementalTask.current} (${pct}%)\n**Continue making progress. Do not refuse or give up.**`);
370+
}
371+
278372
// 6. Remaining plan steps
279373
const remaining = this.taskPlan.filter(s => !s.completed);
280374
if (remaining.length > 0) {
@@ -291,8 +385,16 @@ class ConversationSummarizer {
291385
if (todoLines) sections.push(`## ACTIVE TASKS\n${todoLines}`);
292386
}
293387

294-
// 7. Instruction
295-
sections.push('## INSTRUCTION\nContinue the task from where you left off. Do not repeat completed work.');
388+
// 7. Instruction — strengthened to prevent refusal
389+
let instruction = '## INSTRUCTION\nContinue the task from where you left off. Do not repeat completed work.';
390+
if (this.incrementalTask || fileProgressKeys.length > 0) {
391+
instruction += '\n\n**CRITICAL: DO NOT REFUSE. DO NOT SAY "I cannot continue."**\n';
392+
instruction += 'Make incremental progress using tools:\n';
393+
instruction += '- Use `append_to_file` to add more content to existing files\n';
394+
instruction += '- Use `write_todos` to track multi-step progress\n';
395+
instruction += '- Call a tool NOW to continue the task\n';
396+
}
397+
sections.push(instruction);
296398

297399
// Assemble and cap
298400
let summary = sections.join('\n\n');

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "guide-ide",
3-
"version": "1.8.25",
3+
"version": "1.8.26",
44
"description": "guIDE - AI-Powered Offline IDE with local LLM, RAG, MCP tools, browser automation, and integrated terminal",
55
"author": {
66
"name": "Brendan Gray",

0 commit comments

Comments
 (0)