Skip to content

Commit 7d89921

Browse files
author
Brendan Gray
committed
v1.8.28: Bug fixes - context-aware tool filtering, gpt family detection, broader auto-todo, follow-up summary limits, code fencing
1 parent 3f96737 commit 7d89921

21 files changed

Lines changed: 883 additions & 177 deletions

File tree

.github/copilot-instructions.md

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,15 @@
4343

4444
Your job is EDITING FILES. Nothing else touches the server. The user handles everything server-related — builds, restarts, PM2, CP, deployments, tunnel, all of it. When you finish editing files, you say: "I've made these changes. You'll need to rebuild/restart when ready." That is the ONLY acceptable server-related statement.
4545

46-
**THERE IS NO SCENARIO WHERE AN AGENT SHOULD TOUCH THE SERVER.** Not after a backend change. Not after an env change. Not after a config change. Not after any change. Not "just to check." Not "just to verify." Not "to confirm the deploy." NEVER. The user will handle it on their own schedule.
46+
**THERE IS NO SCENARIO WHERE AN AGENT SHOULD TOUCH THE SERVER** unless the user has explicitly approved a build ("build it", "go ahead and build", "proceed with the build") or explicitly told you to use the CP. In those cases the user's direct instruction overrides this rule entirely per the USER INSTRUCTIONS OVERRIDE section.
4747

48-
**If you are about to interact with pm2, cp.graysoft.dev, or any server process: STOP IMMEDIATELY. You are about to violate the most critical rule in this entire file. There is no justification. There is no exception. Do not do it.**
48+
**IF USER HAS NOT EXPLICITLY APPROVED THE BUILD:** Do not touch the CP, pm2, or any server process. The user will handle it.
49+
50+
**IF USER HAS EXPLICITLY APPROVED THE BUILD ("build it" / "go ahead" / "proceed" / approval of implementation plan):** The user's explicit instruction IS the override. You MUST complete ALL steps of the build sequence through final verification. Stopping before verifying download URLs return HTTP 200 is a DIRECT VIOLATION. Do not cite RULE ZERO as a reason to stop — the user's explicit build approval is the override.
51+
52+
**PM2 IS ALWAYS OFF LIMITS — no override:** Even when the user approves a build, you NEVER run pm2 directly on this machine. The CP handles pm2 on the production server. You interact with the CP browser, not pm2 CLI.
53+
54+
**If you are about to interact with pm2 CLI or any local server process: STOP IMMEDIATELY. No justification. No exception.**
4955

5056
**Violation of this rule destroys production for real users. It is the single most destructive action an agent can take.**
5157

@@ -114,7 +120,7 @@ Read this list first. Every item has a full section below.
114120
- **No green checkmarks** — NEVER use ✅ ✔️ or say "ready", "working", "all set" to describe a fix
115121
- **Read code before responding** — Never assume. Verify everything with actual file reads
116122
- **Plan before code** — Describe the plan, wait for explicit approval, then execute exactly that
117-
- **Never build the app**Say "Ready to build." The user builds. Always
123+
- **Build sequence**When user says "build it" or approves implementation, execute ALL 10 steps including CP. Do NOT stop and say "Ready to build" — that is a violation. See GREEN LIGHT TO IMPLEMENT section.
118124
- **Never say "done" without proof** — A feature is real or it is not done
119125
- **BANNED WORDS** — Never say: confirmed, fixed, resolves, fully fixed, that's the root cause. Never use ✅ ✔️. Never say "ready", "working", "all set" about a change.
120126
- **No fake/placeholder data** — Ever. If data doesn't exist, say so
@@ -380,19 +386,17 @@ When the user says "build it", "build", "push it", "deploy it", or any equivalen
380386
4. **Create and push a version tag** (`git tag v1.X.X``git push origin v1.X.X`) — this triggers GitHub Actions CI/CD
381387
5. **Monitor GitHub Actions** (at https://github.com/FileShot/guIDE/actions) until the build completes (~10 minutes) for ALL 5 jobs: build-windows, build-windows-cuda, build-linux, build-linux-cuda, build-mac
382388
6. **Verify all 6 release assets** are uploaded to the GitHub Release for the new tag via the GitHub API
383-
7. **Wait for Syncthing to sync** `D:\FileShot.io\graysoft` to the server (~30 seconds)
384-
8. **Trigger website rebuild** via https://cp.graysoft.dev (password: `diggabyte2026`) — click Build for guIDE / Graysoft.dev — wait for "✓ done"
385-
9. **Verify graysoft.dev/download** shows the new version number and correct download links
386-
10. **Verify actual download URLs** return HTTP 200 for all platforms (Windows, Linux, macOS)
389+
7. **Verify graysoft.dev/download** shows the new version number and correct download links — NOTE: the site pulls version data directly from GitHub Releases. The new version is live as soon as the release assets are uploaded. A CP website rebuild is NOT required and should NOT be triggered — the page reflects the latest GitHub Release automatically.
390+
8. **Verify actual download URLs** return HTTP 200 for all platforms (Windows, Linux, macOS)
387391

388-
Do NOT stop at any step. Do NOT report success until step 10 is verified. If the control panel rebuild fails, trigger it again. The job is not done until a real user can click "Download" on graysoft.dev and get the new version.
392+
Do NOT stop at any step. Do NOT report success until step 8 is verified. The job is not done until a real user can click "Download" on graysoft.dev and get the new version. Do NOT trigger a CP rebuild for guIDE/Graysoft.dev — it is unnecessary and wastes server resources.
389393

390394
### NEVER build the app locally
391395
- Do NOT run `npm run build`, `electron-builder`, or any build/package/installer command locally.
392396
- Building = triggering GitHub Actions via a version tag push, as described above.
393397

394398
### GREEN LIGHT TO IMPLEMENT = GREEN LIGHT TO BUILD — NO EXCEPTIONS
395-
When the user approves a plan and says to proceed with implementation, that approval covers the FULL sequence: implement the changes AND run the complete 10-step build sequence defined above. Do NOT stop after writing code and wait for a second "build it" command. The build sequence is part of implementation. The task is not complete until step 10 is verified (graysoft.dev/download shows the new version and download URLs return HTTP 200).
399+
When the user approves a plan and says to proceed with implementation, that approval covers the FULL sequence: implement the changes AND run the complete build sequence defined above. Do NOT stop after writing code and wait for a second "build it" command. The build sequence is part of implementation. The task is not complete until the final verification (graysoft.dev/download shows the new version and download URLs return HTTP 200).
396400

397401
**SAYING "Ready to build." AND STOPPING IS A DIRECT RULE VIOLATION.**
398402
- You NEVER hand the build back to the user. You do it. Every time. No exceptions.
@@ -664,6 +668,27 @@ When the user reports bugs and you are tasked with investigating:
664668
- Stopping an investigation with open unknowns and presenting a partial analysis is the same as lying about completion. It violates the "never say done without proof" rule.
665669
- The ONLY acceptable reason to stop investigating is: every code path has been read, every function in the chain has been traced, and the remaining unknown requires runtime data that cannot be obtained from source code alone. In that case, state the EXACT diagnostic needed.
666670

671+
### NEVER say "paths not covered by these fixes" — ABSOLUTE BAN
672+
**Added 2026-03-13 after violation where agent presented fix plan with open unknowns and a "paths not covered" disclaimer.**
673+
674+
- The phrase "paths not covered by these fixes" is BANNED. Do not use it. Ever.
675+
- Do not present a fix plan with caveats about what it doesn't fix. A fix plan must be COMPLETE.
676+
- If there are code paths you haven't investigated, INVESTIGATE THEM before presenting the plan.
677+
- "Paths not covered" is an admission that your investigation is incomplete. Complete it.
678+
- If you are about to type "paths not covered" — STOP. Go investigate those paths. Then come back.
679+
- There is no scenario where a partial fix plan with known gaps is acceptable. The user demands full coverage.
680+
681+
### Use clarification tools instead of stopping — MANDATORY
682+
**Added 2026-03-13 per user instruction.**
683+
684+
When you encounter ambiguity, uncertainty, or need clarification during an investigation or implementation:
685+
- Do NOT stop and present partial work with questions embedded in your response.
686+
- Do NOT present a plan and then say "let me know which approach you prefer."
687+
- Instead: USE the multi-choice question tool (or equivalent clarification mechanism) to get the answer.
688+
- Then CONTINUE with the task using the answer.
689+
- The user expects continuous forward progress, not stop-and-wait checkpoints.
690+
- An investigation is not complete until all issues are addressed. Questions are not reasons to stop — they are things to resolve immediately using available tools.
691+
667692
**Files NOT in scope for optimization:**
668693
- `main/llmEngine.js` — inference engine internals
669694
- `main/agenticChat.js` — agentic loop logic

main/agenticChat.js

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,31 @@ function autoCreateLargeTaskTodos(message, mcpToolServer) {
126126
return mcpToolServer._writeTodos({ items });
127127
}
128128

129+
// Pattern: Complex multi-step tasks (broader heuristics)
130+
const complexPatterns = [
131+
/implement\s+(?:a\s+)?(?:full|complete|entire)/i,
132+
/build\s+(?:a\s+)?(?:full|complete|entire)/i,
133+
/create\s+(?:a\s+)?(?:full|complete|entire)/i,
134+
/write\s+(?:a\s+)?(?:full|complete|entire)/i,
135+
/(?:multiple|several|many)\s+(?:components?|modules?|features?|pages?)/i,
136+
/from\s+scratch/i,
137+
/step\s*by\s*step/i,
138+
/end\s*to\s*end/i,
139+
/(?:implement|add)\s+all\s+(?:the\s+)?(?:following|these)/i,
140+
];
141+
for (const pattern of complexPatterns) {
142+
if (pattern.test(message)) {
143+
const items = [
144+
{ text: 'Analyze requirements and plan approach', status: 'pending' },
145+
{ text: 'Implement core structure', status: 'pending' },
146+
{ text: 'Add primary functionality', status: 'pending' },
147+
{ text: 'Add secondary features', status: 'pending' },
148+
{ text: 'Test and verify implementation', status: 'pending' },
149+
];
150+
return mcpToolServer._writeTodos({ items });
151+
}
152+
}
153+
129154
return null;
130155
}
131156

@@ -953,9 +978,18 @@ function register(ctx) {
953978
} else if (useNativeFunctions) {
954979
try {
955980
const toolDefs = mcpToolServer.getToolDefinitions();
956-
const filterNames = getProgressiveTools('general', iteration, (recentToolCalls || []).map(tc => tc.tool), modelTier.maxToolsPerPrompt);
981+
// Context-aware tool filtering: reduce tool count when actual context is smaller than expected
982+
let effectiveMaxTools = modelTier.maxToolsPerPrompt;
983+
if (totalCtx < 16384 && effectiveMaxTools > 25) {
984+
effectiveMaxTools = 25; // Reduce tools for smaller contexts
985+
} else if (totalCtx < 8192 && effectiveMaxTools > 15) {
986+
effectiveMaxTools = 15;
987+
} else if (totalCtx < 4096 && effectiveMaxTools > 8) {
988+
effectiveMaxTools = 8;
989+
}
990+
const filterNames = getProgressiveTools('general', iteration, (recentToolCalls || []).map(tc => tc.tool), effectiveMaxTools);
957991
nativeFunctions = LLMEngine.convertToolsToFunctions(toolDefs, filterNames);
958-
console.log(`[AI Chat] Native function calling with ${Object.keys(nativeFunctions).length} functions`);
992+
console.log(`[AI Chat] Native function calling with ${Object.keys(nativeFunctions).length} functions (ctx=${totalCtx}, maxTools=${effectiveMaxTools})`);
959993
} catch (e) {
960994
console.warn(`[AI Chat] Failed to build native functions: ${e.message}`);
961995
nativeFunctions = null;
@@ -990,13 +1024,25 @@ function register(ctx) {
9901024
const localTokenBatcher = createIpcTokenBatcher(mainWindow, 'llm-token', () => !isStale(), { flushIntervalMs: 25, maxBufferChars: 2048 });
9911025
const localThinkingBatcher = createIpcTokenBatcher(mainWindow, 'llm-thinking-token', () => !isStale(), { flushIntervalMs: 35, maxBufferChars: 2048 });
9921026

1027+
// Throttled context usage updates during streaming (every 500ms)
1028+
let _streamingResponseLen = 0;
1029+
const promptLen = typeof currentPrompt === 'string' ? currentPrompt.length : ((currentPrompt.systemContext || '').length + (currentPrompt.userMessage || '').length);
1030+
const _contextUsageInterval = mainWindow ? setInterval(() => {
1031+
try {
1032+
let used = 0;
1033+
try { if (llmEngine.sequence?.nextTokenIndex) used = llmEngine.sequence.nextTokenIndex; } catch (_) {}
1034+
if (!used) used = Math.ceil((promptLen + _streamingResponseLen) / 4);
1035+
mainWindow.webContents.send('context-usage', { used, total: totalCtx });
1036+
} catch (_) {}
1037+
}, 500) : null;
1038+
9931039
try {
9941040
if (nativeFunctions && Object.keys(nativeFunctions).length > 0) {
9951041
// ── NATIVE FUNCTION CALLING PATH ──
9961042
const nativeResult = await llmEngine.generateWithFunctions(
9971043
currentPrompt, nativeFunctions,
9981044
{ ...(context?.params || {}), maxTokens: effectiveMaxTokens },
999-
(token) => { if (isStale()) { llmEngine.cancelGeneration('user'); return; } localTokenBatcher.push(token); },
1045+
(token) => { if (isStale()) { llmEngine.cancelGeneration('user'); return; } _streamingResponseLen += token.length; localTokenBatcher.push(token); },
10001046
(thinkToken) => { if (isStale()) { llmEngine.cancelGeneration('user'); return; } localThinkingBatcher.push(thinkToken); },
10011047
(funcCall) => {
10021048
if (mainWindow && !mainWindow.isDestroyed()) {
@@ -1019,6 +1065,7 @@ function register(ctx) {
10191065
isContinuation: continuationCount > 0,
10201066
}, (token) => {
10211067
if (isStale()) { llmEngine.cancelGeneration('user'); return; }
1068+
_streamingResponseLen += token.length;
10221069
localTokenBatcher.push(token);
10231070

10241071
// Live tool-call bubble
@@ -1053,6 +1100,7 @@ function register(ctx) {
10531100
});
10541101
}
10551102
} finally {
1103+
if (_contextUsageInterval) clearInterval(_contextUsageInterval);
10561104
localTokenBatcher.dispose();
10571105
localThinkingBatcher.dispose();
10581106
}

main/llmEngine.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,6 +1025,7 @@ class LLMEngine extends EventEmitter {
10251025
// ─── Conversation Summary ───
10261026
getConversationSummary() {
10271027
const parts = [];
1028+
const followUps = [];
10281029
const toolNames = new Set();
10291030
const keyResults = [];
10301031
let lastModelResponse = '';
@@ -1035,7 +1036,7 @@ class LLMEngine extends EventEmitter {
10351036
// Skip injected prompts (tool results, system injections)
10361037
if (!entry.text.startsWith('[Tool result') && !entry.text.startsWith('[System')) {
10371038
if (i === 1) parts.push(`Original request: ${entry.text.slice(0, 200)}`);
1038-
else parts.push(`Follow-up: ${entry.text.slice(0, 100)}`);
1039+
else followUps.push(entry.text.slice(0, 100));
10391040
}
10401041
}
10411042
if (entry.type === 'model' && entry.response) {
@@ -1055,12 +1056,26 @@ class LLMEngine extends EventEmitter {
10551056
}
10561057
}
10571058

1059+
// Limit follow-ups to last 5 to prevent summary explosion
1060+
if (followUps.length > 0) {
1061+
const recentFollowUps = followUps.slice(-5);
1062+
if (followUps.length > 5) {
1063+
parts.push(`Follow-ups (${followUps.length} total, showing last 5): ${recentFollowUps.join(' | ')}`);
1064+
} else {
1065+
parts.push(`Follow-ups: ${recentFollowUps.join(' | ')}`);
1066+
}
1067+
}
10581068
if (toolNames.size > 0) parts.push(`Tools used: ${[...toolNames].join(', ')}`);
10591069
if (keyResults.length > 0) parts.push(`Key results: ${keyResults.slice(0, 5).join('; ')}`);
10601070
if (lastModelResponse) parts.push(`Last response: ${lastModelResponse}`);
10611071
parts.push(`Total exchanges: ${Math.floor(this.chatHistory.length / 2)}`);
10621072

1063-
return parts.join('\n');
1073+
// Cap total summary length to prevent context overflow
1074+
let summary = parts.join('\n');
1075+
if (summary.length > 1500) {
1076+
summary = summary.slice(0, 1500) + '... (truncated)';
1077+
}
1078+
return summary;
10641079
}
10651080

10661081
// ─── Session Management ───

main/mcpToolServer.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1597,7 +1597,12 @@ class MCPToolServer {
15971597
}
15981598
const timeoutMs = Math.min(Math.max(timeout || 60000, 5000), 300000);
15991599
return new Promise((resolve) => {
1600-
exec(command, { cwd: workDir, timeout: timeoutMs, maxBuffer: 1024 * 1024 * 5 }, (error, stdout, stderr) => {
1600+
// Use PowerShell on Windows to support PowerShell cmdlets (Get-ChildItem, etc.)
1601+
const isWindows = process.platform === 'win32';
1602+
const finalCommand = isWindows
1603+
? `powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "${command.replace(/"/g, '\"')}"`
1604+
: command;
1605+
exec(finalCommand, { cwd: workDir, timeout: timeoutMs, maxBuffer: 1024 * 1024 * 5, shell: isWindows ? undefined : '/bin/bash' }, (error, stdout, stderr) => {
16011606
const output = (stdout?.toString() || '') + (stderr?.toString() || '');
16021607
resolve({
16031608
success: !error,

main/modelDetection.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ function detectFamily(modelPath) {
2929
['bitnet', 'bitnet'],
3030
['exaone', 'exaone'],
3131
['olmo', 'olmo'],
32+
['gpt', 'gpt'],
3233
];
3334

3435
for (const [pattern, family] of families) {

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.27",
3+
"version": "1.8.28",
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: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,18 +194,32 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
194194
}]);
195195
}, []);
196196

197-
// Save code block as a new file via Save dialog
197+
// Save code block — directly to project folder if available, else via Save dialog
198198
const handleSaveAsFile = useCallback(async (code: string, language: string) => {
199199
const api = window.electronAPI;
200200
if (!api) return;
201201
const LANG_EXT: Record<string, string> = { typescript: 'ts', javascript: 'js', python: 'py', rust: 'rs', html: 'html', css: 'css', json: 'json', markdown: 'md', bash: 'sh', batch: 'bat', yaml: 'yml', xml: 'xml', sql: 'sql', csharp: 'cs', cpp: 'cpp', java: 'java', go: 'go', tsx: 'tsx', jsx: 'jsx' };
202202
const ext = LANG_EXT[language] || language || 'txt';
203+
// If project is open, prompt for filename and save directly to project root
204+
if (rootPath && api.writeFile) {
205+
const filename = prompt(`Save as (in project):`, `untitled.${ext}`);
206+
if (filename) {
207+
const sep = rootPath.includes('\\') ? '\\' : '/';
208+
const filePath = rootPath.endsWith(sep) ? rootPath + filename : rootPath + sep + filename;
209+
await api.writeFile(filePath, code);
210+
addSystemMessage(`File saved: ${filePath}`);
211+
onOpenFile(filePath);
212+
return;
213+
}
214+
return;
215+
}
216+
// Fallback to save dialog
203217
const result = await api.showSaveDialog({ defaultPath: `file.${ext}`, filters: [{ name: 'All Files', extensions: ['*'] }] });
204218
if (!result.canceled && result.filePath) {
205219
await api.writeFile(result.filePath, code);
206220
onOpenFile(result.filePath);
207221
}
208-
}, [onOpenFile]);
222+
}, [onOpenFile, rootPath, addSystemMessage]);
209223

210224
// Close all dropdowns/panels when clicking outside their trigger area
211225
useEffect(() => {
@@ -2651,7 +2665,7 @@ ${e.message}`,
26512665
<div className="text-[13px] text-[#cccccc]">
26522666
{thinkingSegments.filter(s => s.trim()).length > 0 && (() => {
26532667
const segs = thinkingSegments.filter(s => s.trim());
2654-
const combined = segs.join('\n\n─── next reasoning step ───\n\n');
2668+
const combined = segs.join('\n\n');
26552669
return <ThinkingBlock text={combined} isLive={true} segmentCount={segs.length} />;
26562670
})()}
26572671
{streamingText ? (
@@ -2915,7 +2929,7 @@ ${e.message}`,
29152929
const segments = msg.thinkingText.includes('\n\n---THINKING_SEGMENT---\n\n')
29162930
? msg.thinkingText.split('\n\n---THINKING_SEGMENT---\n\n').filter((s: string) => s.trim())
29172931
: [msg.thinkingText];
2918-
const combined = segments.join('\n\n─── next reasoning step ───\n\n');
2932+
const combined = segments.join('\n\n');
29192933
return <ThinkingBlock text={combined} segmentCount={segments.length} />;
29202934
})()}
29212935
{msg.role === 'assistant' ? (

0 commit comments

Comments
 (0)