Skip to content

Commit ece42e9

Browse files
author
Brendan Gray
committed
v1.8.20: Fix duplicate tool bubbles, thinking duplication, fetch_webpage misuse
1 parent cfc466a commit ece42e9

4 files changed

Lines changed: 48 additions & 13 deletions

File tree

main/agenticChat.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,8 @@ function register(ctx) {
11861186
if (continuationCount > 0) {
11871187
displayChunk = displayChunk.replace(/\[(?:Continue your response|You were generating a tool call)[\s\S]*?\]/gi, '');
11881188
}
1189+
// Save length before adding this iteration's text (for Bug 2 fix: remove reasoning that gets routed to thinking)
1190+
const priorDisplayLen = displayResponseText.length;
11891191
displayResponseText += displayChunk;
11901192

11911193
// Correct UI stream buffer: the overlapping tokens were already streamed
@@ -1485,7 +1487,10 @@ function register(ctx) {
14851487
}
14861488
if (planningText) {
14871489
mainWindow.webContents.send('llm-thinking-token', planningText);
1488-
mainWindow.webContents.send('llm-replace-last', planningText);
1490+
// Bug 2 fix: Wipe reasoning from stream buffer (it's now in thinking panel only)
1491+
mainWindow.webContents.send('llm-replace-last', '');
1492+
// Also remove from displayResponseText so it doesn't appear in committed message
1493+
displayResponseText = displayResponseText.substring(0, priorDisplayLen);
14891494
}
14901495
}
14911496

@@ -1560,8 +1565,11 @@ function register(ctx) {
15601565

15611566
if (isStale()) break;
15621567

1563-
// Accumulate tool results
1564-
allToolResults.push(...toolResults.results);
1568+
// Filter out deferred results from UI pipeline — they haven't executed yet
1569+
const uiToolResults = toolResults.results.filter(tr => !tr._deferred);
1570+
1571+
// Accumulate only non-deferred tool results for UI
1572+
allToolResults.push(...uiToolResults);
15651573
capArray(allToolResults, 50);
15661574

15671575
// Compress old tool results
@@ -1590,8 +1598,8 @@ function register(ctx) {
15901598
executionState.update(tr.tool, tr.params, tr.result, iteration);
15911599
}
15921600

1593-
// UI events
1594-
sendToolExecutionEvents(mainWindow, toolResults.results, playwrightBrowser, { checkSuccess: true });
1601+
// UI events — send only non-deferred results to prevent duplicate bubbles
1602+
sendToolExecutionEvents(mainWindow, uiToolResults, playwrightBrowser, { checkSuccess: true });
15951603

15961604
// Build tool feedback
15971605
const toolFeedback = buildToolFeedback(toolResults.results, {
@@ -1606,7 +1614,7 @@ function register(ctx) {
16061614
snapFeedback = `\n### Page snapshot after ${snapResult.triggerTool}\n${snapResult.snapshotText}\n\n**${snapResult.elementCount} elements.** Use [ref=N] with browser_click/type.\n`;
16071615
}
16081616

1609-
if (mainWindow) mainWindow.webContents.send('mcp-tool-results', toolResults.results);
1617+
if (mainWindow) mainWindow.webContents.send('mcp-tool-results', uiToolResults);
16101618
fullResponseText += toolFeedback + snapFeedback;
16111619
if (fullResponseText.length > MAX_RESPONSE_SIZE) {
16121620
fullResponseText = fullResponseText.substring(fullResponseText.length - MAX_RESPONSE_SIZE);
@@ -1943,7 +1951,7 @@ async function executeNativeToolCalls(opts) {
19431951

19441952
// Write deferral
19451953
if (shouldDefer && DATA_WRITE_TOOLS.has(call.tool)) {
1946-
results.push({ tool: call.tool, params: call.params, result: { success: false, error: 'DEFERRED: Re-issue write next turn using actual data from tool results.' } });
1954+
results.push({ tool: call.tool, params: call.params, _deferred: true, result: { success: false, error: 'DEFERRED: Re-issue write next turn using actual data from tool results.' } });
19471955
continue;
19481956
}
19491957

main/constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ You can also answer general questions, help with writing, and have normal conver
6262
- **Never output full file content as code blocks in chat.** Always use the appropriate tool: write_file for new files, edit_file for modifications, append_to_file for additions, read_file before editing. Code blocks are only for brief snippets or explanations.
6363
- edit_file: call read_file first to get the exact current text, then supply precise oldText
6464
- Browser: browser_navigate → browser_snapshot → browser_click/type using refs from snapshot
65+
- When creating files (HTML, CSS, JS, etc.), write all content directly. Reference external resources (fonts, libraries, images) via CDN links — do not use fetch_webpage to download them.
6566
- Multi-step tasks (3+ steps): use write_todos to plan, update_todo as each step completes`;
6667

6768
const DEFAULT_COMPACT_PREAMBLE = `You are a helpful, knowledgeable AI assistant. You can answer questions on ANY topic — coding, history, science, math, creative writing, and more. You also have coding tools available. Call tools to take action — never just describe what you'd do. Never refuse a request because it is not about code. Always attempt to fulfill the user's request regardless of its length or complexity. When the user specifies output format or file structure, follow those requirements exactly.
@@ -85,6 +86,7 @@ read_file, write_file, edit_file, list_directory, find_files, grep_search, run_c
8586
- If a tool fails, retry once with corrected parameters.
8687
- For edits: call read_file first, then edit_file with exact oldText and newText.
8788
- For large files: write_file first section, then append_to_file for each remaining section.
89+
- When creating files (HTML, CSS, JS, etc.), write all content directly. Reference external resources (fonts, libraries, images) via CDN links — do not use fetch_webpage to download them.
8890
- If the user asks for multiple files, create ALL of them. Call write_file for EACH file — do not stop after the first file. Do not claim a file was created unless you received a success result from write_file for that specific file. Do not summarize until every requested file exists.
8991
- Always use the exact filename the user specifies.
9092
- Once ALL parts of the task are complete (every requested file written, every question answered), respond with a brief summary. Do not call more tools after the task is done.`;

main/mcpToolServer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ class MCPToolServer {
286286
},
287287
{
288288
name: 'fetch_webpage',
289-
description: 'Fetch and extract content from a specific URL.',
289+
description: 'Fetch and extract text content from a webpage URL. Use ONLY to READ information from a web page (articles, documentation, data). Do NOT use to download CSS, fonts, images, or other resources for files you are creating — reference them via CDN links instead.',
290290
parameters: {
291291
url: { type: 'string', description: 'URL to fetch', required: true },
292292
},

src/components/Chat/ChatPanel.tsx

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,26 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
437437
});
438438
}
439439
if (finished.length > 0) {
440-
setCompletedStreamingTools(prev => [...prev, ...finished]);
440+
// Bug 1 fix: Dedup write tools by tool+filePath, keeping latest entry
441+
setCompletedStreamingTools(prev => {
442+
const combined = [...prev, ...finished];
443+
const writeTools = ['write_file', 'create_file', 'edit_file', 'append_to_file'];
444+
const seen = new Map<string, number>();
445+
// Find latest index for each write tool+filePath combo
446+
for (let i = combined.length - 1; i >= 0; i--) {
447+
const td = combined[i];
448+
if (writeTools.includes(td.tool)) {
449+
const key = `${td.tool}:${td.params?.filePath || td.params?.fileName || ''}`;
450+
if (!seen.has(key)) seen.set(key, i);
451+
}
452+
}
453+
// Keep non-write tools + only the latest of each write tool+filePath
454+
return combined.filter((td, idx) => {
455+
if (!writeTools.includes(td.tool)) return true;
456+
const key = `${td.tool}:${td.params?.filePath || td.params?.fileName || ''}`;
457+
return seen.get(key) === idx;
458+
});
459+
});
441460
}
442461
executingToolsRef.current = [];
443462
setExecutingTools([]);
@@ -2769,16 +2788,22 @@ ${e.message}`,
27692788
const doneExt = doneFilePath.includes('.') ? doneFilePath.split('.').pop()?.toLowerCase() || '' : '';
27702789
const doneLang = LANG_MAP_LIVE[doneExt] || doneExt || 'code';
27712790
const doneContent = toolData.params?.content as string | undefined;
2791+
// Bug 1 fix: Use actual result status instead of hardcoded 'ok'
2792+
const isStreamOk = (toolData as any).result?.success !== false;
27722793
return (
27732794
<div key={`done-flat-${i}`} className="mt-2">
27742795
<div className="flex items-center gap-1.5 mb-1 px-0.5">
2775-
<Check size={11} className="text-[#89d185] flex-shrink-0" />
2776-
<span className="text-[11px] text-[#d4d4d4] font-medium">{getToolLabel(toolData, 'ok')}</span>
2796+
{isStreamOk ? (
2797+
<Check size={11} className="text-[#89d185] flex-shrink-0" />
2798+
) : (
2799+
<X size={11} className="text-[#f14c4c] flex-shrink-0" />
2800+
)}
2801+
<span className="text-[11px] text-[#d4d4d4] font-medium">{getToolLabel(toolData, isStreamOk ? 'ok' : 'fail')}</span>
27772802
</div>
27782803
{doneContent ? (
27792804
<CodeBlock code={doneContent} language={doneLang} onApply={() => onApplyCode(currentFile, doneContent)} isToolCall={true} />
27802805
) : (
2781-
<div className="text-[11px] text-[#89d185] px-0.5">Written</div>
2806+
<div className={`text-[11px] px-0.5 ${isStreamOk ? 'text-[#89d185]' : 'text-[#f14c4c]'}`}>{isStreamOk ? 'Written' : 'Failed'}</div>
27822807
)}
27832808
</div>
27842809
);
@@ -2811,7 +2836,7 @@ ${e.message}`,
28112836
? (typeof rdResult === 'object' ? JSON.stringify(rdResult, null, 2).substring(0, 600) : String(rdResult).substring(0, 600))
28122837
: 'Completed';
28132838
return (
2814-
<CollapsibleToolBlock key={`done-${i}`} label={getToolLabel(toolData, 'ok')} icon="✓">
2839+
<CollapsibleToolBlock key={`done-${i}`} label={getToolLabel(toolData, rdIsOk ? 'ok' : 'fail')} icon={rdIsOk ? '✓' : '✗'}>
28152840
<div>
28162841
{toolData.params && Object.keys(toolData.params).length > 0 && (
28172842
<>

0 commit comments

Comments
 (0)