Skip to content

Commit 87559a2

Browse files
author
Brendan Gray
committed
v1.8.7: 10 fixes — RAG memory exclusion, explorer reindex, Phi-4-mini detection, context threshold, isReady guard, dispose settle, continuation improvements, write dedup, dead import cleanup
1 parent 72609c7 commit 87559a2

7 files changed

Lines changed: 51 additions & 27 deletions

File tree

main/agenticChat.js

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ const {
3434
} = require('./agenticChatHelpers');
3535
const { LLMEngine } = require('./llmEngine');
3636
const { repairToolCalls: repairToolCallsFn } = require('./tools/toolParser');
37-
const { ContextManager } = require('./contextManager');
3837

3938
/**
4039
* Get the path to node-llama-cpp that works in both dev and production (asar).
@@ -495,6 +494,21 @@ function register(ctx) {
495494
const { llmEngine, mcpToolServer, playwrightBrowser, browserManager, ragEngine, memoryStore, ConversationSummarizer, DEFAULT_SYSTEM_PREAMBLE, DEFAULT_COMPACT_PREAMBLE } = ctx;
496495

497496
const modelStatus = llmEngine.getStatus();
497+
498+
// Wait for model to be ready before proceeding (prevents "Model not ready" errors)
499+
if (!llmEngine.isReady) {
500+
console.log('[AI Chat] Model not ready — waiting…');
501+
const readyTimeout = Date.now() + 15000;
502+
while (!llmEngine.isReady && Date.now() < readyTimeout) {
503+
await new Promise(r => setTimeout(r, 500));
504+
}
505+
if (!llmEngine.isReady) {
506+
if (mainWindow) mainWindow.webContents.send('llm-token', '\n*[Model is still loading — please wait and try again]*\n');
507+
if (mainWindow) mainWindow.webContents.send('llm-done');
508+
return;
509+
}
510+
}
511+
498512
const hwContextSize = modelStatus.modelInfo?.contextSize || 32768;
499513

500514
const estimateTokens = (text) => Math.ceil((text || '').length / 3.5);
@@ -539,7 +553,7 @@ function register(ctx) {
539553
// Preamble
540554
const savedSettings = _readConfig()?.userSettings;
541555
const userPreamble = savedSettings?.systemPrompt?.trim();
542-
const contextIsConstrained = totalCtx < 4096;
556+
const contextIsConstrained = totalCtx < 8192;
543557
const isSmallModel = modelProfile.prompt.style === 'compact' || contextIsConstrained;
544558
const defaultPreamble = isSmallModel ? (DEFAULT_COMPACT_PREAMBLE || DEFAULT_SYSTEM_PREAMBLE) : DEFAULT_SYSTEM_PREAMBLE;
545559
const preamble = userPreamble || defaultPreamble;
@@ -551,7 +565,7 @@ function register(ctx) {
551565
const toolPromptStyle = modelProfile.prompt.toolPromptStyle;
552566
const useCompactTools = toolPromptStyle === 'grammar-only' || toolPromptStyle === 'compact' || contextIsConstrained;
553567
if (useCompactTools) {
554-
const compactHint = totalCtx < 4096
568+
const compactHint = totalCtx < 8192
555569
? mcpToolServer.getCompactToolHint(effectiveTaskType, { minimal: true })
556570
: mcpToolServer.getCompactToolHint(effectiveTaskType);
557571
appendIfBudget(compactHint + '\n');
@@ -1150,16 +1164,19 @@ function register(ctx) {
11501164

11511165
let continuationMsg;
11521166
const taskHint = message ? `[Task: ${message.substring(0, 100)}${message.length > 100 ? '...' : ''}]\n` : '';
1167+
// Include written-files manifest so model knows what already exists
1168+
const writtenPaths = Object.keys(writeFileHistory).filter(k => writeFileHistory[k].count > 0);
1169+
const fileManifest = writtenPaths.length > 0
1170+
? `[Files already written this turn: ${writtenPaths.join(', ')}. Do NOT write to these files again. Use append_to_file or edit_file if changes needed.]\n`
1171+
: '';
11531172
if (_hasUnclosedToolFence) {
11541173
const partialFence = _stitchedForMcp.slice(_fenceIdx);
11551174
_pendingPartialBlock = partialFence;
1156-
// FIX-B: Only send short tail (500 chars) to prevent prompt bloat
1157-
const tail = partialFence.length > 500 ? '\u2026' + partialFence.slice(-500) : partialFence;
1158-
continuationMsg = `${taskHint}[Continue the tool call JSON from exactly where it was cut. Output ONLY the JSON continuation. Do NOT restart the tool call. Continue from:\n${tail}]`;
1175+
const tail = partialFence.length > 1000 ? '\u2026' + partialFence.slice(-1000) : partialFence;
1176+
continuationMsg = `${taskHint}${fileManifest}[Continue the tool call JSON from exactly where it was cut. Output ONLY the JSON continuation. Do NOT restart the tool call. Continue from:\n${tail}]`;
11591177
} else {
1160-
// FIX: Use 500 chars of tail (not 200) for better continuation context
1161-
const tail = responseText.length > 500 ? '\u2026' + responseText.slice(-500) : responseText;
1162-
continuationMsg = `${taskHint}[Continue your response exactly where you left off. Do not restart or repeat content. Here is the end of what you wrote:\n${tail}]`;
1178+
const tail = responseText.length > 1000 ? '\u2026' + responseText.slice(-1000) : responseText;
1179+
continuationMsg = `${taskHint}${fileManifest}[Continue your response exactly where you left off. Do not restart or repeat content. Here is the end of what you wrote:\n${tail}]`;
11631180
}
11641181

11651182
currentPrompt = {
@@ -1215,12 +1232,13 @@ function register(ctx) {
12151232
toolResults = await executeNativeToolCalls({
12161233
nativeFunctionCalls, responseText, mcpToolServer, modelTier,
12171234
writeFileHistory, allToolResults, gatheredWebData,
1218-
isStale, waitWhilePaused,
1235+
isStale, waitWhilePaused, continuationCount,
12191236
});
12201237
} else {
12211238
const textOpts = {
12221239
toolPaceMs: 50, skipWriteDeferral: modelTier.tier === 'tiny',
12231240
userMessage: message, lastDroppedFilePaths: _pendingDroppedFilePaths, writeFileHistory,
1241+
continuationCount,
12241242
};
12251243
toolResults = await mcpToolServer.processResponse(_stitchedForMcp, textOpts);
12261244
_pendingDroppedFilePaths = toolResults.droppedFilePaths || [];
@@ -1604,7 +1622,7 @@ function preGenerationContextCheck(opts) {
16041622
*/
16051623
async function executeNativeToolCalls(opts) {
16061624
const { nativeFunctionCalls, responseText, mcpToolServer, modelTier,
1607-
writeFileHistory, allToolResults, gatheredWebData, isStale, waitWhilePaused } = opts;
1625+
writeFileHistory, allToolResults, gatheredWebData, isStale, waitWhilePaused, continuationCount } = opts;
16081626

16091627
// Normalize to {tool, params} format
16101628
let calls = nativeFunctionCalls.map(fc => ({
@@ -1654,11 +1672,12 @@ async function executeNativeToolCalls(opts) {
16541672
if (call.tool.startsWith('browser_')) call.params = mcpToolServer._normalizeBrowserParams(call.tool, call.params);
16551673
else call.params = mcpToolServer._normalizeFsParams(call.tool, call.params);
16561674

1657-
// Cross-iteration write dedup
1675+
// Cross-iteration write dedup (stricter during continuation)
16581676
if (call.tool === 'write_file') {
16591677
const filePath = call.params?.filePath || call.params?.path;
1660-
if (filePath && writeFileHistory[filePath]?.count >= 2) {
1661-
results.push({ tool: call.tool, params: call.params, result: { success: false, error: `BLOCKED: "${filePath}" already written ${writeFileHistory[filePath].count} times.` } });
1678+
const writeLimit = (continuationCount || 0) > 0 ? 1 : 2;
1679+
if (filePath && writeFileHistory[filePath]?.count >= writeLimit) {
1680+
results.push({ tool: call.tool, params: call.params, result: { success: false, error: `BLOCKED: "${filePath}" already written ${writeFileHistory[filePath].count} times. Use append_to_file or edit_file instead.` } });
16621681
continue;
16631682
}
16641683
}

main/ipc/fileSystemHandlers.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ function register(ctx) {
4747
await fs.writeFile(filePath, finalContent, 'utf8');
4848
const win = ctx.getMainWindow();
4949
if (win) win.webContents.send('files-changed');
50+
if (ctx.scheduleIncrementalReindex) ctx.scheduleIncrementalReindex();
5051
try { require('./liveServerHandlers').notifyReload(); } catch {}
5152
return { success: true };
5253
} catch (error) { return { success: false, error: error.message }; }
@@ -80,47 +81,47 @@ function register(ctx) {
8081

8182
ipcMain.handle('create-directory', async (_, dirPath) => {
8283
if (!ctx.isPathAllowed(dirPath)) return { success: false, error: 'Access denied: path outside allowed directories' };
83-
try { await fs.mkdir(dirPath, { recursive: true }); return { success: true }; }
84+
try { await fs.mkdir(dirPath, { recursive: true }); if (ctx.scheduleIncrementalReindex) ctx.scheduleIncrementalReindex(); return { success: true }; }
8485
catch (error) { return { success: false, error: error.message }; }
8586
});
8687

8788
ipcMain.handle('delete-file', async (_, filePath) => {
8889
if (!ctx.isPathAllowed(filePath)) return { success: false, error: 'Access denied: path outside allowed directories' };
89-
try { await fs.unlink(filePath); return { success: true }; }
90+
try { await fs.unlink(filePath); if (ctx.scheduleIncrementalReindex) ctx.scheduleIncrementalReindex(); return { success: true }; }
9091
catch (error) { return { success: false, error: error.message }; }
9192
});
9293

9394
ipcMain.handle('delete-directory', async (_, dirPath) => {
9495
if (!ctx.isPathAllowed(dirPath)) return { success: false, error: 'Access denied: path outside allowed directories' };
95-
try { await fs.rm(dirPath, { recursive: true, force: true }); return { success: true }; }
96+
try { await fs.rm(dirPath, { recursive: true, force: true }); if (ctx.scheduleIncrementalReindex) ctx.scheduleIncrementalReindex(); return { success: true }; }
9697
catch (error) { return { success: false, error: error.message }; }
9798
});
9899

99100
ipcMain.handle('copy-file', async (_, src, dest) => {
100101
if (!ctx.isPathAllowed(src)) return { success: false, error: 'Access denied: source path outside allowed directories' };
101102
if (!ctx.isPathAllowed(dest)) return { success: false, error: 'Access denied: destination path outside allowed directories' };
102-
try { await fs.copyFile(src, dest); return { success: true }; }
103+
try { await fs.copyFile(src, dest); if (ctx.scheduleIncrementalReindex) ctx.scheduleIncrementalReindex(); return { success: true }; }
103104
catch (error) { return { success: false, error: error.message }; }
104105
});
105106

106107
ipcMain.handle('copy-directory', async (_, src, dest) => {
107108
if (!ctx.isPathAllowed(src)) return { success: false, error: 'Access denied: source path outside allowed directories' };
108109
if (!ctx.isPathAllowed(dest)) return { success: false, error: 'Access denied: destination path outside allowed directories' };
109-
try { await fs.cp(src, dest, { recursive: true }); return { success: true }; }
110+
try { await fs.cp(src, dest, { recursive: true }); if (ctx.scheduleIncrementalReindex) ctx.scheduleIncrementalReindex(); return { success: true }; }
110111
catch (error) { return { success: false, error: error.message }; }
111112
});
112113

113114
ipcMain.handle('move-file', async (_, src, dest) => {
114115
if (!ctx.isPathAllowed(src)) return { success: false, error: 'Access denied: source path outside allowed directories' };
115116
if (!ctx.isPathAllowed(dest)) return { success: false, error: 'Access denied: destination path outside allowed directories' };
116-
try { await fs.rename(src, dest); return { success: true }; }
117+
try { await fs.rename(src, dest); if (ctx.scheduleIncrementalReindex) ctx.scheduleIncrementalReindex(); return { success: true }; }
117118
catch (error) { return { success: false, error: error.message }; }
118119
});
119120

120121
ipcMain.handle('rename-file', async (_, oldPath, newPath) => {
121122
if (!ctx.isPathAllowed(oldPath)) return { success: false, error: 'Access denied: source path outside allowed directories' };
122123
if (!ctx.isPathAllowed(newPath)) return { success: false, error: 'Access denied: destination path outside allowed directories' };
123-
try { await fs.rename(oldPath, newPath); return { success: true }; }
124+
try { await fs.rename(oldPath, newPath); if (ctx.scheduleIncrementalReindex) ctx.scheduleIncrementalReindex(); return { success: true }; }
124125
catch (error) { return { success: false, error: error.message }; }
125126
});
126127

main/llmEngine.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ class LLMEngine extends EventEmitter {
224224
}
225225
// Extra settle time for node-llama-cpp internal async ops (_eraseContextTokenRanges etc.)
226226
// that may still be in-flight after the generation promise resolves
227-
await new Promise(r => setTimeout(r, 150));
227+
await new Promise(r => setTimeout(r, 500));
228228
await this._dispose();
229229

230230
if (loadSignal.aborted) throw new Error('Load cancelled');

main/mcpToolServer.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2476,9 +2476,10 @@ class MCPToolServer {
24762476
}
24772477
if (options.writeFileHistory && call.tool === 'write_file') {
24782478
const wfPath = call.params?.filePath || call.params?.path || call.params?.file_path;
2479-
if (wfPath && options.writeFileHistory[wfPath] && options.writeFileHistory[wfPath].count >= 2) {
2479+
const wfLimit = (options.continuationCount || 0) > 0 ? 1 : 2;
2480+
if (wfPath && options.writeFileHistory[wfPath] && options.writeFileHistory[wfPath].count >= wfLimit) {
24802481
console.log(`[MCP] Write dedup: blocking ${call.tool} to "${wfPath}" (already written ${options.writeFileHistory[wfPath].count}x)`);
2481-
results.push({ tool: call.tool, params: call.params, result: { success: false, error: `BLOCKED: "${wfPath}" has already been written ${options.writeFileHistory[wfPath].count} times this conversation. The file is COMPLETE. Do NOT write to it again.` } });
2482+
results.push({ tool: call.tool, params: call.params, result: { success: false, error: `BLOCKED: "${wfPath}" has already been written ${options.writeFileHistory[wfPath].count} times this conversation. Use append_to_file or edit_file instead.` } });
24822483
continue;
24832484
}
24842485
}
@@ -2548,9 +2549,10 @@ class MCPToolServer {
25482549
}
25492550
if (options.writeFileHistory && call.tool === 'write_file') {
25502551
const wfPath = call.params?.filePath || call.params?.path || call.params?.file_path;
2551-
if (wfPath && options.writeFileHistory[wfPath] && options.writeFileHistory[wfPath].count >= 2) {
2552+
const wfLimit = (options.continuationCount || 0) > 0 ? 1 : 2;
2553+
if (wfPath && options.writeFileHistory[wfPath] && options.writeFileHistory[wfPath].count >= wfLimit) {
25522554
console.log(`[MCP] Write dedup: blocking ${call.tool} to "${wfPath}" (already written ${options.writeFileHistory[wfPath].count}x)`);
2553-
results.push({ tool: call.tool, params: call.params, result: { success: false, error: `BLOCKED: "${wfPath}" has already been written ${options.writeFileHistory[wfPath].count} times this conversation. The file is COMPLETE. Do NOT write to it again.` } });
2555+
results.push({ tool: call.tool, params: call.params, result: { success: false, error: `BLOCKED: "${wfPath}" has already been written ${options.writeFileHistory[wfPath].count} times this conversation. Use append_to_file or edit_file instead.` } });
25542556
continue;
25552557
}
25562558
}

main/modelDetection.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ function detectParamSize(modelPath) {
5555
}
5656

5757
// Phi model fallbacks (version-based size inference)
58+
if (base.includes('phi-4') && base.includes('mini')) return 3.8;
5859
if (base.includes('phi-4')) return 14;
5960
if (base.includes('phi-3') && base.includes('mini')) return 3.8;
6061
if (base.includes('phi-3') && base.includes('medium')) return 14;

main/ragEngine.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const IGNORE_RE = [
2929
/\.bin$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.svg$/, /\.woff/,
3030
/\.ttf$/, /\.eot$/, /\.mp[34]$/, /\.avi$/, /\.zip$/, /\.tar$/, /\.gz$/,
3131
/\.rar$/, /\.7z$/, /package-lock\.json$/, /yarn\.lock$/, /pnpm-lock\.yaml$/,
32+
/\.guide-memory/, /\.ide-memory/,
3233
];
3334

3435
/* ── RAGEngine ───────────────────────────────────────────────────── */

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.6",
3+
"version": "1.8.7",
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)