Skip to content

Commit 5881495

Browse files
author
Brendan Gray
committed
v1.8.44: Fix forward projection, live context ring, maxIterations=100, isStuck removal, write_file guard removal
Fix F1: Reduce forward projection to 60% of respBudget (premature rotation at 66%) Fix F2: Live context ring updates every 200 tokens during streaming Fix F3: Raise default maxIterations from 50 to 100 Fix E1: Remove isStuck() loop detector (banned classifier, caused code blocks to vanish) Fix E2: Remove _filesWrittenThisTurn guard (blocked legitimate write_file retries) Fix D: COMPACT_PREAMBLE revisions, markdown table rendering, code block padding
1 parent 3bfd31f commit 5881495

23 files changed

Lines changed: 2868 additions & 3031 deletions

main/agenticChat.js

Lines changed: 28 additions & 2415 deletions
Large diffs are not rendered by default.

main/agenticChatHelpers.js

Lines changed: 43 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,14 @@ function checkFileCompleteness(content, filePath) {
1717
const trimmedEnd = content.trimEnd();
1818
const lastCodeLine = trimmedEnd.split('\n').pop().trim();
1919
const ext = (filePath?.match(/\.([^.]+)$/) || [])[1] || '';
20+
// Non-code files (markdown, text, json, yaml, env, etc.) have no structural close tag.
21+
// They are always considered "complete" — no forced continuation for these types.
22+
if (/^(md|txt|json|ya?ml|toml|env|gitignore|csv|tsv|log|ini|cfg|conf|xml|svg|lock)$/i.test(ext)) {
23+
return true;
24+
}
2025
let looksComplete = false;
2126
if (/^html?$/i.test(ext)) {
22-
// Fix 42: Anchor to end of content — </html> must be near the end, not just anywhere in the string.
27+
// Anchor to end of content — </html> must be near the end, not just anywhere in the string.
2328
// Without $, a </html> inside a JS string/template in the middle of the file triggers a false positive.
2429
looksComplete = /<\/html\s*>\s*$/i.test(trimmedEnd);
2530
} else if (/^css$/i.test(ext)) {
@@ -226,8 +231,27 @@ function enrichErrorFeedback(toolName, error, failCounts = {}) {
226231
return tips.length > 0 ? `\nSuggestion: ${tips[0]}` : '';
227232
}
228233

234+
/**
235+
* Compress a single text string: prune large code fences and page snapshots.
236+
* Returns the compressed string, or the original if compression didn't achieve ≥30% reduction.
237+
*/
238+
function _compressText(text) {
239+
if (!text || text.length < 800) return null;
240+
let compressed = text;
241+
compressed = compressed.replace(
242+
/```[\s\S]{800,}?```/g,
243+
(match) => `\`\`\`\n[${(match.match(/\n/g) || []).length} lines — pruned]\n\`\`\``
244+
);
245+
compressed = compressed.replace(
246+
/\*\*Page Snapshot\*\*\s*\([^)]*\):\n[\s\S]{500,}?(?=\n\*\*|\n###|\n---|$)/g,
247+
() => `**Page Snapshot**: [pruned for context]`
248+
);
249+
return compressed.length < text.length * 0.7 ? compressed : null;
250+
}
251+
229252
/**
230253
* Prune verbose messages in chat history to free context space.
254+
* Handles both local format ({ type, text, response[] }) and cloud format ({ content }).
231255
*/
232256
function pruneVerboseHistory(chatHistory, keepRecentCount = 6) {
233257
if (!Array.isArray(chatHistory) || chatHistory.length <= keepRecentCount + 1) return 0;
@@ -239,72 +263,39 @@ function pruneVerboseHistory(chatHistory, keepRecentCount = 6) {
239263
const msg = chatHistory[i];
240264
if (!msg) continue;
241265

242-
// Handle model responses: { type: 'model', response: [text] }
266+
// Local format: model responses with response[] array
243267
if (msg.type === 'model' && Array.isArray(msg.response)) {
244268
let changed = false;
245269
for (let ri = 0; ri < msg.response.length; ri++) {
246-
const r = msg.response[ri];
247-
if (typeof r !== 'string' || r.length < 800) continue;
248-
let compressed = r;
249-
compressed = compressed.replace(
250-
/```[\s\S]{800,}?```/g,
251-
(match) => `\`\`\`\n[${(match.match(/\n/g) || []).length} lines — pruned]\n\`\`\``
252-
);
253-
if (compressed.length < r.length * 0.7) {
254-
msg.response[ri] = compressed;
255-
changed = true;
256-
}
270+
const compressed = _compressText(msg.response[ri]);
271+
if (compressed) { msg.response[ri] = compressed; changed = true; }
257272
}
258273
if (changed) pruned++;
259274
continue;
260275
}
261276

262-
// Handle user/system messages: { type: 'user'|'system', text: '...' }
263-
if (!msg.text || msg.text.length < 800) continue;
264-
265-
let compressed = msg.text;
266-
compressed = compressed.replace(
267-
/```[\s\S]{800,}?```/g,
268-
(match) => `\`\`\`\n[${(match.match(/\n/g) || []).length} lines — pruned]\n\`\`\``
269-
);
270-
compressed = compressed.replace(
271-
/\*\*Page Snapshot\*\*\s*\([^)]*\):\n[\s\S]{500,}?(?=\n\*\*|\n###|\n---|$)/g,
272-
(match) => `**Page Snapshot**: [pruned for context]`
273-
);
274-
275-
if (compressed.length < msg.text.length * 0.7) {
276-
chatHistory[i] = { ...msg, text: compressed };
277-
pruned++;
277+
// Local format: user/system messages with text field
278+
if (msg.text) {
279+
const compressed = _compressText(msg.text);
280+
if (compressed) { chatHistory[i] = { ...msg, text: compressed }; pruned++; }
281+
continue;
282+
}
283+
284+
// Cloud format: messages with content field
285+
if (msg.content) {
286+
const compressed = _compressText(msg.content);
287+
if (compressed) { chatHistory[i] = { ...msg, content: compressed }; pruned++; }
278288
}
279289
}
280290
return pruned;
281291
}
282292

283293
/**
284294
* Prune verbose messages in cloud conversation history.
295+
* Delegates to the unified pruneVerboseHistory.
285296
*/
286297
function pruneCloudHistory(history, keepRecentCount = 6) {
287-
if (!Array.isArray(history) || history.length <= keepRecentCount + 1) return 0;
288-
289-
let pruned = 0;
290-
const cutoff = history.length - keepRecentCount;
291-
292-
for (let i = 1; i < cutoff; i++) {
293-
const msg = history[i];
294-
if (!msg || !msg.content || msg.content.length < 800) continue;
295-
296-
let compressed = msg.content;
297-
compressed = compressed.replace(
298-
/```[\s\S]{800,}?```/g,
299-
(match) => `\`\`\`\n[${(match.match(/\n/g) || []).length} lines — pruned]\n\`\`\``
300-
);
301-
302-
if (compressed.length < msg.content.length * 0.7) {
303-
history[i] = { ...msg, content: compressed };
304-
pruned++;
305-
}
306-
}
307-
return pruned;
298+
return pruneVerboseHistory(history, keepRecentCount);
308299
}
309300

310301
/**
@@ -458,7 +449,7 @@ function formatSuccessfulToolResult(tr, opts = {}) {
458449
case 'read_file':
459450
text += `**File:** ${tr.params?.filePath}${tr.result.readRange ? ` (lines ${tr.result.readRange})` : ''}\n`;
460451
{
461-
// Fix 58B: Show head+tail for large files so the model can see both
452+
// Show head+tail for large files so the model can see both
462453
// the file structure AND where it left off (critical for append workflows)
463454
const content = tr.result.content || '';
464455
if (content.length > 4000) {
@@ -510,7 +501,7 @@ function formatSuccessfulToolResult(tr, opts = {}) {
510501
}
511502
}
512503

513-
// Fix 59C: Post-write structural validation — immediate feedback loop
504+
// Post-write structural validation — immediate feedback loop
514505
// Provides IDE-level diagnostics (like LSP Problems panel) so the model
515506
// knows the structural state of the file RIGHT AFTER writing, not just at rotation.
516507
const writtenFilePath = tr.result?.path || tr.params?.filePath || '';

0 commit comments

Comments
 (0)