Skip to content

Commit f9c4290

Browse files
author
Brendan Gray
committed
Fix 26: CodeBlock defaultCollapsed toggle visibility + Fix 27: looksComplete salvage heuristic file-extension-aware (prevents false-positive on HTML mid-CSS)
1 parent 7391b5a commit f9c4290

7 files changed

Lines changed: 417 additions & 95 deletions

File tree

main/agenticChat.js

Lines changed: 197 additions & 21 deletions
Large diffs are not rendered by default.

main/agenticChatHelpers.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ function createIpcTokenBatcher(mainWindow, channel, canSend, opts = {}) {
113113
try {
114114
if (!mainWindow || mainWindow.isDestroyed() || !mainWindow.webContents || mainWindow.webContents.isDestroyed()) return;
115115
if (typeof canSend === 'function' && !canSend()) return;
116+
// Token batch logging: log what the model generates so we can diagnose
117+
// generation stalls, false-close bugs, and unexpected content mid-stream.
118+
if (channel === 'llm-token') {
119+
const preview = text.length > 120 ? text.slice(0, 80) + '…' + text.slice(-20) : text;
120+
console.log('[LLM-BATCH]', JSON.stringify(preview));
121+
}
116122
mainWindow.webContents.send(channel, text);
117123
} catch (_) {}
118124
};

main/mcpToolServer.js

Lines changed: 74 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2411,40 +2411,26 @@ class MCPToolServer {
24112411
}
24122412
}
24132413

2414-
// Param inference: fix "." or "" filePath when user message references a real file
2414+
// Param inference: fix "." or "" filePath for file operations when user message references a real file
2415+
// NOTE: list_directory is excluded — ".", "./", and "" all resolve correctly to the project root
2416+
// in _listDirectory via path.join(projectPath, "."). Keyword extraction from user messages
2417+
// was removed because it's a classifier pattern that mis-triggers on common words like "build".
24152418
if (options.userMessage) {
24162419
for (const call of toolCalls) {
24172420
if (!call || typeof call.tool !== 'string') continue;
24182421
const fp = call.params?.filePath ?? call.params?.path ?? call.params?.file_path ?? '';
24192422
const isFileOp = ['read_file', 'write_file', 'edit_file'].includes(call.tool);
2420-
const isListOp = call.tool === 'list_directory';
24212423
const pathIsBad = fp === '.' || fp === './' || fp === '' || fp === '..';
24222424

2423-
if (pathIsBad && (isFileOp || isListOp)) {
2425+
if (pathIsBad && isFileOp) {
24242426
const msg = options.userMessage;
2425-
if (isFileOp) {
2426-
const fileMatch = msg.match(/\b([\w.-]+\.(?:json|js|ts|tsx|jsx|md|html|css|yml|yaml|toml|py|sh|bat|txt|xml|env|cfg|conf|ini|log|csv))\b/i);
2427-
if (fileMatch) {
2428-
const inferred = fileMatch[1];
2429-
console.log(`[MCP] Param inference: "${call.tool}" filePath "${fp}" → "${inferred}" (from user message)`);
2430-
call.params = { ...call.params, filePath: inferred };
2431-
if (call.params.path) delete call.params.path;
2432-
if (call.params.file_path) delete call.params.file_path;
2433-
}
2434-
} else if (isListOp) {
2435-
const dirMatch = msg.match(/\b(src|main|scripts|tests|components|services|utils|config|public|build|dist|output|lib|assets)\b/i);
2436-
if (dirMatch) {
2437-
const inferred = dirMatch[1].toLowerCase();
2438-
const dp = call.params?.dirPath ?? call.params?.path ?? call.params?.directory ?? '.';
2439-
if (dp === '.' || dp === '' || dp === './') {
2440-
console.log(`[MCP] Param inference: "list_directory" path "${dp}" → "${inferred}" (from user message)`);
2441-
call.params = { ...call.params };
2442-
if (call.params.dirPath != null) call.params.dirPath = inferred;
2443-
else if (call.params.directory != null) call.params.directory = inferred;
2444-
else if (call.params.path != null) call.params.path = inferred;
2445-
else call.params.dirPath = inferred;
2446-
}
2447-
}
2427+
const fileMatch = msg.match(/\b([\w.-]+\.(?:json|js|ts|tsx|jsx|md|html|css|yml|yaml|toml|py|sh|bat|txt|xml|env|cfg|conf|ini|log|csv))\b/i);
2428+
if (fileMatch) {
2429+
const inferred = fileMatch[1];
2430+
console.log(`[MCP] Param inference: "${call.tool}" filePath "${fp}" → "${inferred}" (from user message)`);
2431+
call.params = { ...call.params, filePath: inferred };
2432+
if (call.params.path) delete call.params.path;
2433+
if (call.params.file_path) delete call.params.file_path;
24482434
}
24492435
}
24502436
}
@@ -2532,7 +2518,37 @@ class MCPToolServer {
25322518
const wfLimit = (options.continuationCount || 0) > 0 ? 1 : 2;
25332519
if (wfPath && options.writeFileHistory[wfPath] && options.writeFileHistory[wfPath].count >= wfLimit) {
25342520
console.log(`[MCP] Write dedup: blocking ${call.tool} to "${wfPath}" (already written ${options.writeFileHistory[wfPath].count}x)`);
2535-
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.` } });
2521+
// Auto-convert: extract new content and append instead of blocking outright
2522+
let autoConverted = false;
2523+
const newContent = call.params?.content || '';
2524+
if (newContent.length > 50) {
2525+
try {
2526+
const _fs = require('fs'), _path = require('path');
2527+
const fullPath = _path.resolve(this.projectPath || '.', wfPath);
2528+
const existing = _fs.existsSync(fullPath) ? _fs.readFileSync(fullPath, 'utf-8') : '';
2529+
if (existing.length > 0) {
2530+
const el = existing.split('\n'), nl = newContent.split('\n');
2531+
let m = 0;
2532+
for (let i = 0; i < Math.min(el.length, nl.length); i++) { if (el[i].trimEnd() === nl[i].trimEnd()) m++; else break; }
2533+
if (m > 5 && nl.length > el.length) {
2534+
const suffix = nl.slice(m).join('\n');
2535+
if (suffix.trim().length > 20) {
2536+
console.log(`[MCP] Write dedup auto-convert: "${wfPath}" (${m}/${el.length} overlap, appending ${nl.length - m} new lines)`);
2537+
const ar = await this.executeTool('append_to_file', { filePath: wfPath, content: suffix });
2538+
results.push({ tool: 'append_to_file', params: { filePath: wfPath, content: '...(auto-converted)' }, result: ar });
2539+
autoConverted = true;
2540+
}
2541+
}
2542+
if (!autoConverted && m > el.length * 0.5) {
2543+
results.push({ tool: call.tool, params: call.params, result: { success: true, message: `File "${wfPath}" already written (${el.length} lines). Move on to next task.` } });
2544+
autoConverted = true;
2545+
}
2546+
}
2547+
} catch (e) { console.warn(`[MCP] Write dedup auto-convert failed: ${e.message}`); }
2548+
}
2549+
if (!autoConverted) {
2550+
results.push({ tool: call.tool, params: call.params, result: { success: false, error: `BLOCKED: "${wfPath}" already written ${options.writeFileHistory[wfPath].count} times. Use append_to_file or edit_file instead.` } });
2551+
}
25362552
continue;
25372553
}
25382554
}
@@ -2605,7 +2621,37 @@ class MCPToolServer {
26052621
const wfLimit = (options.continuationCount || 0) > 0 ? 1 : 2;
26062622
if (wfPath && options.writeFileHistory[wfPath] && options.writeFileHistory[wfPath].count >= wfLimit) {
26072623
console.log(`[MCP] Write dedup: blocking ${call.tool} to "${wfPath}" (already written ${options.writeFileHistory[wfPath].count}x)`);
2608-
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.` } });
2624+
// Auto-convert: extract new content and append instead of blocking outright
2625+
let autoConverted = false;
2626+
const newContent = call.params?.content || '';
2627+
if (newContent.length > 50) {
2628+
try {
2629+
const _fs = require('fs'), _path = require('path');
2630+
const fullPath = _path.resolve(this.projectPath || '.', wfPath);
2631+
const existing = _fs.existsSync(fullPath) ? _fs.readFileSync(fullPath, 'utf-8') : '';
2632+
if (existing.length > 0) {
2633+
const el = existing.split('\n'), nl = newContent.split('\n');
2634+
let m = 0;
2635+
for (let i = 0; i < Math.min(el.length, nl.length); i++) { if (el[i].trimEnd() === nl[i].trimEnd()) m++; else break; }
2636+
if (m > 5 && nl.length > el.length) {
2637+
const suffix = nl.slice(m).join('\n');
2638+
if (suffix.trim().length > 20) {
2639+
console.log(`[MCP] Write dedup auto-convert: "${wfPath}" (${m}/${el.length} overlap, appending ${nl.length - m} new lines)`);
2640+
const ar = await this.executeTool('append_to_file', { filePath: wfPath, content: suffix });
2641+
results.push({ tool: 'append_to_file', params: { filePath: wfPath, content: '...(auto-converted)' }, result: ar });
2642+
autoConverted = true;
2643+
}
2644+
}
2645+
if (!autoConverted && m > el.length * 0.5) {
2646+
results.push({ tool: call.tool, params: call.params, result: { success: true, message: `File "${wfPath}" already written (${el.length} lines). Move on to next task.` } });
2647+
autoConverted = true;
2648+
}
2649+
}
2650+
} catch (e) { console.warn(`[MCP] Write dedup auto-convert failed: ${e.message}`); }
2651+
}
2652+
if (!autoConverted) {
2653+
results.push({ tool: call.tool, params: call.params, result: { success: false, error: `BLOCKED: "${wfPath}" already written ${options.writeFileHistory[wfPath].count} times. Use append_to_file or edit_file instead.` } });
2654+
}
26092655
continue;
26102656
}
26112657
}

0 commit comments

Comments
 (0)