Skip to content

Commit 11117d0

Browse files
author
Brendan Gray
committed
feat: v1.6.7 - response flicker fix, code block preview in tool cards, deploy automation
1 parent 16e78a0 commit 11117d0

8 files changed

Lines changed: 121 additions & 41 deletions

File tree

electron-main.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ app.commandLine.appendSwitch('disable-gpu-vsync'); // Reduce input latency
2929

3030
// ── V8 Performance Flags ──
3131
// Enable V8 code caching for faster require() on subsequent launches
32-
app.commandLine.appendSwitch('js-flags', '--optimize-for-size --max-old-space-size=4096');
32+
// NOTE: --optimize-for-size is intentionally absent — it shrinks compiled machine code
33+
// at the cost of execution speed, which is the wrong trade-off for a dev tool.
34+
app.commandLine.appendSwitch('js-flags', '--max-old-space-size=4096');
3335

3436
// ─── Single Instance Lock ─────────────────────────────────────────────
3537
// Prevent multiple instances from competing for config files, model files,

main/agenticChat.js

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -785,7 +785,10 @@ function register(ctx) {
785785
const hwContextSize = modelStatus.modelInfo?.contextSize || 32768;
786786

787787
// Helper functions (defined early — needed for budget calculation)
788-
const estimateTokens = (text) => Math.ceil((text || '').length / 4);
788+
// /3.5 gives ~14% more conservative token estimate than /4 — real LLM tokenizers
789+
// produce 3–3.5 chars/token for code and JSON (shorter than English prose).
790+
// This prevents buildStaticPrompt + buildDynamicContext from overcommitting budget.
791+
const estimateTokens = (text) => Math.ceil((text || '').length / 3.5);
789792

790793
// ── ModelProfile-driven budgeting ──
791794
// The ModelProfile registry provides effective context size, response reserve %,
@@ -931,11 +934,13 @@ function register(ctx) {
931934
// Dynamic context: memory, RAG, file, error — changes between iterations.
932935
// Injected into user message instead of system context to avoid KV cache invalidation.
933936
// Chat mode: skip ALL dynamic context to maximize conversation space.
934-
const buildDynamicContext = (taskTypeOverride) => {
937+
// budgetOverride: optional cap for dynamic context tokens — used by overflow retry
938+
// to shed memory/RAG/file context while preserving tools and preamble.
939+
const buildDynamicContext = (taskTypeOverride, budgetOverride) => {
935940
const effectiveTaskType = taskTypeOverride || taskType;
936941
// Chat mode: no dynamic context injection — keep the full context for conversation
937942
if (effectiveTaskType === 'chat') return '';
938-
let tokenBudget = Math.floor(maxPromptTokens * 0.4); // Reserve budget for dynamic context
943+
let tokenBudget = budgetOverride !== undefined ? budgetOverride : Math.floor(maxPromptTokens * 0.4); // default: 40% of prompt budget
939944
let prompt = '';
940945

941946
const appendIfBudget = (text, label) => {
@@ -1469,9 +1474,20 @@ function register(ctx) {
14691474
try { await llmEngine.resetSession(true); } catch (_) {}
14701475
sessionJustRotated = true;
14711476
const rotatedBase = buildStaticPrompt();
1477+
// Fix C: use 10% of prompt budget for dynamic context on retry — drops memory/RAG/file
1478+
// context but keeps tools and preamble fully intact. Prevents repeat overflow on
1479+
// small-context models without touching the model's tool access.
1480+
// Fix D: if partial content was generated before the overflow, inject it so the model
1481+
// continues from where it left off rather than restarting the response from scratch.
1482+
const _firstTurnPartial = fullResponseText.trim().length > 0
1483+
? fullResponseText.substring(Math.max(0, fullResponseText.length - 1500))
1484+
: '';
1485+
const _firstTurnHint = _firstTurnPartial
1486+
? `\n\nYou were generating a response and the context was reset due to size constraints. Here is the end of what you wrote:\n---\n${_firstTurnPartial}\n---\nContinue directly from where you left off without repeating what you already wrote.`
1487+
: '';
14721488
currentPrompt = {
14731489
systemContext: rotatedBase,
1474-
userMessage: buildDynamicContext() + '\n' + message
1490+
userMessage: buildDynamicContext(undefined, Math.floor(maxPromptTokens * 0.10)) + '\n' + message + _firstTurnHint
14751491
};
14761492
continue;
14771493
}
@@ -1570,9 +1586,17 @@ function register(ctx) {
15701586
}
15711587

15721588
const rotatedBase = buildStaticPrompt();
1589+
// Fix D: include the end of what was generated so far so the model continues
1590+
// seamlessly rather than restarting the response after context rotation.
1591+
const _rotationPartial = fullResponseText.trim().length > 0
1592+
? fullResponseText.substring(Math.max(0, fullResponseText.length - 1500))
1593+
: '';
1594+
const _rotationHint = _rotationPartial
1595+
? `\n\nYou were generating a response and context was rotated. Here is the end of what you wrote:\n---\n${_rotationPartial}\n---\nContinue directly from where you left off without repeating what you already wrote.`
1596+
: `\nContext was rotated. The current user request is: ${message.substring(0, 300)}${message.length > 300 ? '...' : ''}`;
15731597
currentPrompt = {
15741598
systemContext: rotatedBase,
1575-
userMessage: buildDynamicContext() + '\n' + convSummary + `\nContext was rotated. The current user request is: ${message.substring(0, 300)}${message.length > 300 ? '...' : ''}`
1599+
userMessage: buildDynamicContext() + '\n' + convSummary + _rotationHint
15761600
};
15771601
sessionJustRotated = true;
15781602
lastConvSummary = convSummary;
@@ -1937,17 +1961,25 @@ function register(ctx) {
19371961
}
19381962

19391963
// ── Strip code-fence artifacts from displayed text ──
1940-
// Re-enabled with per-iteration offset tracking (llm-iteration-begin + iterationStartOffsetRef).
1941-
// The frontend prepends prior iterations' text so only the current iteration's portion is replaced.
1964+
// Route any conversational planning text to the thinking panel, then wipe the
1965+
// main chat iteration slot clean. This prevents raw JSON tool calls from flashing
1966+
// in the chat bubble and matches the cloud path behavior.
19421967
if (toolResults.hasToolCalls && toolResults.results.length > 0 && mainWindow) {
1943-
let cleaned = responseText;
1944-
cleaned = cleaned.replace(/```(?:tool_call|tool|json)[^\n]*\n[\s\S]*?```/g, '');
1945-
cleaned = cleaned.replace(/<tool_call>[\s\S]*?<\/tool_call>/g, '');
1946-
cleaned = cleaned.replace(/\{\s*"(?:tool|name)"\s*:\s*"[^"]+"\s*,\s*"(?:params|arguments)"[\s\S]*?\}\s*\}/g, '');
1947-
cleaned = cleaned.replace(/\n{3,}/g, '\n\n').trim();
1948-
if (cleaned !== responseText) {
1949-
mainWindow.webContents.send('llm-replace-last', cleaned);
1950-
}
1968+
// Extract planning text — everything the model wrote before the first tool call indicator
1969+
const toolIndicators = ['{"tool":', '```tool_call', '```json\n{"tool"', '<tool_call>'];
1970+
let splitIdx = responseText.length;
1971+
for (const indicator of toolIndicators) {
1972+
const idx = responseText.indexOf(indicator);
1973+
if (idx >= 0 && idx < splitIdx) splitIdx = idx;
1974+
}
1975+
const planningText = responseText.substring(0, splitIdx).trim();
1976+
if (planningText) {
1977+
// Planning text belongs in the thinking panel, not the main chat bubble
1978+
mainWindow.webContents.send('llm-thinking-token', planningText);
1979+
}
1980+
// Wipe this iteration's streamed content from main chat — the final answer
1981+
// streams clean in the last iteration that produces no tool calls.
1982+
mainWindow.webContents.send('llm-replace-last', '');
19511983
}
19521984

19531985
if (!toolResults.hasToolCalls || toolResults.results.length === 0) {

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.6.4",
3+
"version": "1.6.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",

src/components/Chat/ChatPanel.tsx

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2456,22 +2456,43 @@ ${e.message}`,
24562456
<div className="text-[11px] text-[#858585]">Completed</div>
24572457
</CollapsibleToolBlock>
24582458
))}
2459-
{executingTools.map((toolData, i) => (
2460-
<CollapsibleToolBlock key={`exec-${i}`} label={getToolLabel(toolData, 'running')} icon="⟳">
2461-
<div>
2462-
<div className="flex items-center gap-2 mb-2">
2463-
<Loader2 size={12} className="animate-spin text-[#007acc]" />
2464-
<span className="text-[11px] text-[#858585]">Executing...</span>
2459+
{executingTools.map((toolData, i) => {
2460+
const isCodeWriteTool = ['write_file', 'create_file', 'edit_file', 'append_to_file'].includes(toolData.tool);
2461+
const codeContent = toolData.params?.content as string | undefined;
2462+
const filePath = ((toolData.params?.filePath || toolData.params?.fileName || '') as string);
2463+
const ext = filePath.includes('.') ? filePath.split('.').pop()?.toLowerCase() || '' : '';
2464+
const langMap: Record<string, string> = {
2465+
ts: 'typescript', tsx: 'tsx', js: 'javascript', jsx: 'jsx',
2466+
py: 'python', rs: 'rust', go: 'go', java: 'java', cs: 'csharp',
2467+
cpp: 'cpp', c: 'c', html: 'html', css: 'css', json: 'json',
2468+
yaml: 'yaml', yml: 'yaml', md: 'markdown', sh: 'bash',
2469+
bat: 'batch', txt: 'text', xml: 'xml', sql: 'sql',
2470+
};
2471+
const language = langMap[ext] || ext || 'code';
2472+
return (
2473+
<CollapsibleToolBlock key={`exec-${i}`} label={getToolLabel(toolData, 'running')} icon="⟳">
2474+
<div>
2475+
<div className="flex items-center gap-2 mb-2">
2476+
<Loader2 size={12} className="animate-spin text-[#007acc]" />
2477+
<span className="text-[11px] text-[#858585]">Executing...</span>
2478+
</div>
2479+
{isCodeWriteTool && codeContent ? (
2480+
<CodeBlock
2481+
code={codeContent}
2482+
language={language}
2483+
onApply={() => {}}
2484+
isToolCall={true}
2485+
/>
2486+
) : toolData.params && Object.keys(toolData.params).length > 0 ? (
2487+
<>
2488+
<div className="text-[10px] text-[#858585] mb-1 font-medium tracking-wide">PARAMETERS</div>
2489+
<pre className="whitespace-pre-wrap text-[11px] font-mono text-[#d4d4d4] bg-[#1e1e1e] rounded-md p-2">{JSON.stringify(toolData.params, null, 2)}</pre>
2490+
</>
2491+
) : null}
24652492
</div>
2466-
{toolData.params && Object.keys(toolData.params).length > 0 && (
2467-
<>
2468-
<div className="text-[10px] text-[#858585] mb-1 font-medium tracking-wide">PARAMETERS</div>
2469-
<pre className="whitespace-pre-wrap text-[11px] font-mono text-[#d4d4d4] bg-[#1e1e1e] rounded-md p-2">{JSON.stringify(toolData.params, null, 2)}</pre>
2470-
</>
2471-
)}
2472-
</div>
2473-
</CollapsibleToolBlock>
2474-
))}
2493+
</CollapsibleToolBlock>
2494+
);
2495+
})}
24752496
</ToolCallGroup>
24762497
</div>
24772498
)}

src/components/Chat/hooks/useChatStreaming.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,24 @@ export function useChatStreaming(): ChatStreamingState {
167167
// Anti-hallucination: backend detected fake tool results
168168
const cleanupReplace = api.onLlmReplaceLast?.((cleanedText: string) => {
169169
if (streamEpochRef.current !== activeEpochRef.current) return;
170-
// Preserve text from prior iterations — only replace current iteration's portion
171170
const prefix = streamBufferRef.current.slice(0, iterationStartOffsetRef.current);
171+
// When cleanedText is empty (tool-call iteration wipe), the streamed planning text
172+
// would vanish abruptly from the main chat. Promote it to a thinking segment so it
173+
// transitions visually rather than disappearing — prevents the jarring flash/blank effect.
174+
if (!cleanedText) {
175+
const iterationText = streamBufferRef.current.slice(iterationStartOffsetRef.current).trim();
176+
if (iterationText.length > 10) {
177+
// Avoid duplication: backend may have already sent this text as llm-thinking-token.
178+
// Only push if last thinking segment doesn't already start with the same content.
179+
const lastSeg = thinkingSegmentsRef.current[thinkingSegmentsRef.current.length - 1] || '';
180+
const firstChunk = iterationText.substring(0, 80);
181+
if (!lastSeg.includes(firstChunk)) {
182+
thinkingSegmentsRef.current.push(iterationText);
183+
scheduleThinkingUpdate();
184+
}
185+
}
186+
}
187+
// Preserve text from prior iterations — only replace current iteration's portion
172188
streamBufferRef.current = prefix + cleanedText;
173189
// Jump display to buffer end — corrections show immediately, no typewriter delay
174190
displayPosRef.current = streamBufferRef.current.length;

website/next.config.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ const nextConfig = {
88
},
99
async headers() {
1010
return [
11+
// Allow the 404 game page to be embedded in same-origin iframes
12+
// (not-found.tsx renders the game in an iframe; DENY would block it)
13+
{
14+
source: '/404-game.html',
15+
headers: [
16+
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
17+
{ key: 'Content-Security-Policy', value: "frame-ancestors 'self'" },
18+
],
19+
},
1120
{
1221
source: '/(.*)',
1322
headers: [

website/public/updates/latest.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
version: 2.4.2
1+
version: 1.6.7
22
files:
3-
- url: guIDE-Setup-2.4.2.exe
4-
sha512: gRkj8j0P6rtrZbK0S8jminTw3PjE/wp7qxdwM85hEFcX4CAOP2srhaChqXy2hh9qNH36PeDr8NDuiQ+rrE3wsg==
5-
size: 187949120
6-
path: guIDE-Setup-2.4.2.exe
7-
sha512: gRkj8j0P6rtrZbK0S8jminTw3PjE/wp7qxdwM85hEFcX4CAOP2srhaChqXy2hh9qNH36PeDr8NDuiQ+rrE3wsg==
8-
releaseDate: '2026-02-26T23:38:09.045Z'
3+
- url: guIDE-Setup-1.6.7.exe
4+
sha512: E1COJgnzT0cFCecAQz4R2zyAufQRSP0CK5WrNo+HaVD0ZHWMsN8ui8ZBONZZtr+YWFRgsBSz0/KTd7Gd2soEYA==
5+
size: 197957257
6+
path: guIDE-Setup-1.6.7.exe
7+
sha512: E1COJgnzT0cFCecAQz4R2zyAufQRSP0CK5WrNo+HaVD0ZHWMsN8ui8ZBONZZtr+YWFRgsBSz0/KTd7Gd2soEYA==
8+
releaseDate: '2026-03-01T05:39:42.895Z'

website/src/app/download/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
// Single source of truth for the displayed release version.
44
// Updated automatically by: npm run release:deploy (from IDE root)
5-
const CURRENT_VERSION = '1.6.4';
5+
const CURRENT_VERSION = '1.6.7';
66

77
import Link from 'next/link';
88
import { useState } from 'react';

0 commit comments

Comments
 (0)