Skip to content

Commit 5f1de02

Browse files
author
Brendan Gray
committed
Release v1.7.0 - grammar toggle off by default, write_file loop fix, streaming code block improvements
1 parent 2db13ee commit 5f1de02

11 files changed

Lines changed: 139 additions & 122 deletions

File tree

.github/copilot-instructions.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
# 🚫 STOP — READ THIS FIRST — NON-NEGOTIABLE — NO EXCEPTIONS
2+
3+
## THIS COMPUTER IS A DEV MACHINE. IT IS NOT A SERVER.
4+
5+
**DO NOT — under ANY circumstances — run any of the following on this machine:**
6+
- `pm2 start` / `pm2 restart` / `pm2 reload` / `pm2 stop` / `pm2 kill` / `pm2 save` / `pm2 resurrect`
7+
- `node server.js` / `npm start` / `npm run dev` (for any site backend or frontend server)
8+
- Create, modify, or trigger any Windows Scheduled Task
9+
- Start any cloudflared tunnel process
10+
- Run any ecosystem config (`ecosystem.config.cjs`, etc.)
11+
12+
**The production server is a SEPARATE PHYSICAL COMPUTER.** It runs all 8 sites. You cannot access it from this terminal. Manage it ONLY via https://cp.graysoft.dev (password: `diggabyte2026`, PIN: `0615`).
13+
14+
**Violating this rule causes production downtime for real users. Every time you start a process here it conflicts with the real server. This is not a warning — it is a hard rule.**
15+
16+
**If you are about to run pm2 or start any server process: STOP. Do not do it. There is no scenario where it is correct.**
17+
18+
---
19+
120
# GitHub Copilot Instructions — guIDE Project
221

322
---
@@ -66,6 +85,7 @@ Read this list first. Every item has a full section below.
6685
- **No half-assing** — Every feature fully implemented end-to-end. No partial implementations
6786
- **No lazy shortcuts** — Write the correct solution even if it takes 500 lines
6887
- **No guessing** — "I don't know" is always acceptable. Speculation presented as fact is not
88+
- **Never suggest without 100% certainty** — If you are not certain, DO NOT suggest. Read more code, read more logs, ask the user what they see. A wrong suggestion is worse than silence.
6989
- **No lying** — Never claim code works without verifying it
7090
- **Think through pros and cons** — Present trade-offs explicitly, let the user decide
7191
- **Respond to problems with solutions** — Don't just acknowledge. Propose and research
@@ -228,6 +248,17 @@ Before declaring any root cause:
228248
- Do not claim code works without verifying it compiles/runs.
229249
- If something failed, say it failed. Do not hide failures.
230250

251+
### NEVER suggest without 100% certainty — ABSOLUTE RULE
252+
**This is non-negotiable. If you are not certain, do not suggest. Silence is better than a wrong suggestion.**
253+
254+
- If you have not read every relevant line of code in the full call chain, you are NOT certain.
255+
- If the user has described behavior that contradicts your hypothesis, YOU ARE WRONG — not the user. Read more code.
256+
- If you cannot trace exactly WHY a bug occurs from source to screen with actual file reads, say "I need to read more code before I can say."
257+
- Do NOT say "it might be X" or "I believe it's Y" and then act on that belief. Uncertainty stated out loud is not permission to proceed.
258+
- Do NOT present a partial understanding as a complete diagnosis.
259+
- A wrong suggestion wastes build time, breaks trust, and violates PATTERN 7 in the recurring failures section.
260+
- The standard: if you were in court and had to swear the suggestion is correct under oath — would you? If not, stay silent and investigate more.
261+
231262
### Honesty Over Helpfulness
232263
- Being genuinely helpful means sometimes saying "there's nothing to do here" or "I don't know how to do this."
233264
- Producing busywork output (fake audits, unnecessary refactors, placeholder features) wastes the user's time and money.

main/agenticChat.js

Lines changed: 59 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -130,108 +130,6 @@ function register(ctx) {
130130
const isStale = () => myRequestId !== _activeRequestId || ctx.agenticCancelled;
131131

132132
try {
133-
// ── Image / Video Generation Detection ──
134-
// If the user is asking for image/video generation, handle it directly instead of routing to an LLM
135-
const imgDetect = ImageGenerationService.detectImageRequest(message);
136-
const vidDetect = ImageGenerationService.detectVideoRequest(message);
137-
138-
if (vidDetect) {
139-
// Attempt video generation via Pollinations (requires free API key)
140-
const imageGen = ctx.imageGen;
141-
if (!imageGen || imageGen._pollinationsKeys.length === 0) {
142-
// No Pollinations API keys — inform user how to enable video gen
143-
if (mainWindow) {
144-
mainWindow.webContents.send('llm-token',
145-
'⚠️ **Video generation requires a free Pollinations API key.**\n\n' +
146-
'1. Go to **https://enter.pollinations.ai** and create a free account\n' +
147-
'2. Copy your API key\n' +
148-
'3. Paste it in **Settings → Pollinations API Key**\n\n' +
149-
'Free video models available: **Seedance** (2-10s, best quality), **Wan** (2-15s, with audio), **Grok Video** (alpha).\n\n' +
150-
'I can **generate a still image** instead if you\'d like — just ask me to "generate an image of …"'
151-
);
152-
}
153-
return { success: true, response: 'Video generation requires Pollinations API key.', isVideoRequest: true };
154-
}
155-
156-
if (mainWindow) {
157-
mainWindow.webContents.send('llm-token', `🎬 *Generating video: "${vidDetect.extractedPrompt.substring(0, 100)}${vidDetect.extractedPrompt.length > 100 ? '…' : ''}"*\n\n⏳ Videos take 30-120 seconds to generate — please be patient…\n\n`);
158-
}
159-
160-
try {
161-
const result = await imageGen.generateVideo(vidDetect.extractedPrompt, {});
162-
163-
if (result.success) {
164-
const videoPayload = JSON.stringify({
165-
type: 'generated-video',
166-
videoBase64: result.videoBase64,
167-
mimeType: result.mimeType,
168-
prompt: result.prompt,
169-
provider: result.provider,
170-
model: result.model,
171-
duration: result.duration,
172-
});
173-
174-
if (mainWindow) {
175-
mainWindow.webContents.send('llm-token', `\n\n<!--GENERATED_VIDEO:${videoPayload}-->\n\n`);
176-
mainWindow.webContents.send('llm-token', `✅ Video generated via **Pollinations AI** (${result.model}). Use the buttons below the video to save it.`);
177-
}
178-
return { success: true, response: 'Video generated successfully.', isVideoGeneration: true, video: result };
179-
} else {
180-
if (mainWindow) {
181-
mainWindow.webContents.send('llm-token', `❌ Video generation failed: ${result.error}\n\nI can **generate a still image** instead — just ask!`);
182-
}
183-
return { success: false, error: result.error, isVideoGeneration: true };
184-
}
185-
} catch (vidErr) {
186-
if (mainWindow) {
187-
mainWindow.webContents.send('llm-token', `❌ Video generation error: ${vidErr.message}\n\nPlease try again.`);
188-
}
189-
return { success: false, error: vidErr.message, isVideoGeneration: true };
190-
}
191-
}
192-
193-
if (imgDetect.isImageRequest) {
194-
const imageGen = ctx.imageGen;
195-
if (mainWindow) {
196-
mainWindow.webContents.send('llm-token', `🎨 *Generating image: "${imgDetect.extractedPrompt.substring(0, 100)}${imgDetect.extractedPrompt.length > 100 ? '…' : ''}"*\n\n`);
197-
}
198-
199-
try {
200-
const result = await imageGen.generate(imgDetect.extractedPrompt, {
201-
width: 1024,
202-
height: 1024,
203-
});
204-
205-
if (result.success) {
206-
// Send a special token that the renderer will parse as an inline image
207-
const imagePayload = JSON.stringify({
208-
type: 'generated-image',
209-
imageBase64: result.imageBase64,
210-
mimeType: result.mimeType,
211-
prompt: result.prompt,
212-
provider: result.provider,
213-
model: result.model,
214-
});
215-
216-
if (mainWindow) {
217-
mainWindow.webContents.send('llm-token', `\n\n<!--GENERATED_IMAGE:${imagePayload}-->\n\n`);
218-
mainWindow.webContents.send('llm-token', `✅ Image generated via **${result.provider === 'pollinations' ? 'Pollinations AI' : 'Google Gemini'}** (${result.model}). Use the buttons below the image to save or discard it.`);
219-
}
220-
return { success: true, response: 'Image generated successfully.', isImageGeneration: true, image: result };
221-
} else {
222-
if (mainWindow) {
223-
mainWindow.webContents.send('llm-token', `❌ Image generation failed: ${result.error}\n\nI can still help you with text-based tasks — just let me know!`);
224-
}
225-
return { success: false, error: result.error, isImageGeneration: true };
226-
}
227-
} catch (imgErr) {
228-
if (mainWindow) {
229-
mainWindow.webContents.send('llm-token', `❌ Image generation error: ${imgErr.message}\n\nPlease try again.`);
230-
}
231-
return { success: false, error: imgErr.message, isImageGeneration: true };
232-
}
233-
}
234-
235133
// ── Auto Mode: automatically pick the best model for this task ──
236134
if (context?.autoMode && !context?.cloudProvider) {
237135
const autoSelect = (() => {
@@ -1346,9 +1244,12 @@ function register(ctx) {
13461244
// - xlarge models (14B+): grammar ON for first 2 iterations (original behavior)
13471245
// The model can still output free text even with grammar constraining enabled —
13481246
// the grammar only ensures that WHEN tool calls are made, they're structurally valid.
1247+
// Grammar toggle: respect user setting (default OFF). When off, skip native function calling
1248+
// entirely and go straight to text mode — avoids generation hangs on small models.
1249+
const grammarEnabled = _readConfig()?.userSettings?.enableGrammar ?? false;
13491250
const grammarIterLimit = modelTier.grammarAlwaysOn ? Infinity
13501251
: modelTier.tier === 'large' ? 5 : 2;
1351-
const useNativeFunctions = (taskType !== 'chat') && iteration <= grammarIterLimit;
1252+
const useNativeFunctions = grammarEnabled && (taskType !== 'chat') && iteration <= grammarIterLimit;
13521253
let nativeFunctions = null;
13531254
if (consecutiveEmptyGrammarRetries >= 1) {
13541255
// Grammar-to-text fallback: model can't produce grammar output, degrade gracefully.
@@ -1420,6 +1321,10 @@ function register(ctx) {
14201321
// Stream tool generation progress to the renderer for live bubble display.
14211322
// The renderer shows a CollapsibleToolBlock with partial params as they stream in.
14221323
if (mainWindow && !mainWindow.isDestroyed()) {
1324+
if (!toolChunk._loggedFirst) {
1325+
toolChunk._loggedFirst = true;
1326+
console.log(`[AI Chat] llm-tool-generating IPC SENT: callIndex=${toolChunk.callIndex} fn=${toolChunk.functionName} paramsLen=${toolChunk.paramsText?.length} done=${toolChunk.done}`);
1327+
}
14231328
mainWindow.webContents.send('llm-tool-generating', toolChunk);
14241329
}
14251330
}
@@ -1431,16 +1336,61 @@ function register(ctx) {
14311336
}
14321337
} else {
14331338
// ── LEGACY TEXT PARSING PATH ──
1339+
// Synthetic llm-tool-generating events: accumulate tokens and fire the same
1340+
// IPC event that grammar mode fires so the live streaming bubble appears.
1341+
// Grammar mode fires this from the toolChunk callback (4th arg of generateWithFunctions).
1342+
// Text mode has no equivalent callback, so we detect the JSON inline here.
1343+
let _tb = ''; // raw token accumulator (text path only)
1344+
let _tIdx = 9000; // callIndex sentinel — grammar mode uses 0-based ints
1345+
let _tStart = -1; // offset of opening '{' of current tool call in _tb
1346+
let _tName = null; // tool name once the key has streamed through
1347+
14341348
result = await llmEngine.generateStream(currentPrompt, {
14351349
...(context?.params || {}),
14361350
maxTokens: effectiveMaxTokens,
14371351
}, (token) => {
14381352
if (isStale()) { llmEngine.cancelGeneration('user'); return; }
14391353
localTokenBatcher.push(token);
1354+
1355+
// ── Live tool-call bubble (text mode) ──
1356+
_tb += token;
1357+
// Step 1: find the opening brace of a tool call if not already tracking one
1358+
if (_tStart === -1) {
1359+
const m = _tb.match(/\{\s*"tool"\s*:\s*"([^"]+)"/);
1360+
if (m) {
1361+
_tStart = m.index;
1362+
_tName = m[1];
1363+
}
1364+
}
1365+
// Step 2: stream the accumulating content to the renderer
1366+
if (_tStart !== -1 && _tName && mainWindow && !mainWindow.isDestroyed()) {
1367+
const raw = _tb.slice(_tStart);
1368+
// Cap at 3000 chars for IPC efficiency; frontend further caps at 1500 for display
1369+
const paramsText = raw.length > 3000 ? raw.slice(0, 3000) + '\n…[truncated]' : raw;
1370+
mainWindow.webContents.send('llm-tool-generating', {
1371+
callIndex: _tIdx,
1372+
functionName: _tName,
1373+
paramsText,
1374+
done: false,
1375+
});
1376+
}
14401377
}, (thinkToken) => {
14411378
if (isStale()) { llmEngine.cancelGeneration('user'); return; }
14421379
localThinkingBatcher.push(thinkToken);
14431380
});
1381+
1382+
// Mark any in-flight text-mode generating bubble as done.
1383+
// In the happy path the tool-executing IPC event clears generatingToolCalls
1384+
// automatically; this done:true handles edge cases where the model wrote a
1385+
// tool call but execution was skipped or cancelled.
1386+
if (_tStart !== -1 && _tName && mainWindow && !mainWindow.isDestroyed()) {
1387+
mainWindow.webContents.send('llm-tool-generating', {
1388+
callIndex: _tIdx,
1389+
functionName: _tName,
1390+
paramsText: '',
1391+
done: true,
1392+
});
1393+
}
14441394
}
14451395
} finally {
14461396
localTokenBatcher.dispose();
@@ -2384,8 +2334,14 @@ function register(ctx) {
23842334
const executionStateBlock = getExecutionStateSummary() || '';
23852335

23862336
const hasBrowserAction = toolResults.results.some(tr => tr.tool && tr.tool.startsWith('browser_'));
2337+
// Context-aware continuation: if all results this iteration were successful file writes,
2338+
// give the model explicit permission to stop rather than commanding another tool call.
2339+
const allSuccessfulWrites = toolResults.results.length > 0 &&
2340+
toolResults.results.every(tr => (tr.tool === 'write_file' || tr.tool === 'create_file') && tr.result?.success === true);
23872341
const continueInstruction = hasBrowserAction
23882342
? `\n\nThe page snapshot above has element [ref=N] numbers. Do NOT call browser_snapshot — you already have it. Use browser_click, browser_type, etc. with [ref=N]. Output your next tool call as a fenced JSON block NOW.`
2343+
: allSuccessfulWrites
2344+
? `\n\nFiles written successfully. If the task is complete, provide a final summary now. Only call another tool if there is genuinely more work remaining that has not been done yet.`
23892345
: `\n\nOutput the next tool call to make progress. Only provide a final summary when ALL steps are fully complete.`;
23902346

23912347
// Build the iteration prompt with structured context ordering:

main/constants.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ const DEFAULT_COMPACT_PREAMBLE = `You are a local AI coding assistant with tools
8383
8484
## Behavior
8585
- **Your tools are real and execute in the live environment.** Call them — do not describe what you would do instead of doing it.
86+
- **When your response would contain a complete file (code, markup, config, data) — call write_file. File content belongs in the filesystem, not in chat.**
8687
- **Never say you created, saved, ran, or navigated to something unless you called a tool that did it.**
8788
- **Never claim you searched for something, looked it up, or checked a source unless you actually called web_search or fetch_webpage in this response.**
8889
- **You do not know today's date or current real-world state. If asked for the date, time, or any live or time-sensitive information — call web_search immediately. Never state a current date, time, or real-world value from memory.**
@@ -104,7 +105,6 @@ const DEFAULT_COMPACT_PREAMBLE = `You are a local AI coding assistant with tools
104105
- Never claim a task is done before calling the tool that completes it — writing a file requires write_file, searching requires web_search
105106
- When read_file fails with ENOENT, call find_files to locate the file by name
106107
- Tool format: {"tool":"read_file","params":{"filePath":"src/app.js"}}
107-
- write_file to save code — never paste file content into chat
108108
- For conversational messages — greetings, casual chat, simple questions — respond directly with text. No tools needed.`;
109109

110110
/**

main/llmEngine.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1828,8 +1828,14 @@ After your brief acknowledgment, output ONLY the tool call blocks — no extra t
18281828
// Accumulate paramsChunk text per callIndex and stream live to UI.
18291829
// This powers the streaming tool generation bubble in the renderer
18301830
// so users can see what the model is writing instead of a blank screen.
1831-
if (!_paramsChunkBufs[chunk.callIndex]) _paramsChunkBufs[chunk.callIndex] = '';
1831+
if (!_paramsChunkBufs[chunk.callIndex]) {
1832+
_paramsChunkBufs[chunk.callIndex] = '';
1833+
console.log(`[LLM] onFunctionCallParamsChunk FIRST FIRE: callIndex=${chunk.callIndex} functionName=${chunk.functionName} done=${chunk.done}`);
1834+
}
18321835
if (chunk.paramsChunk) _paramsChunkBufs[chunk.callIndex] += chunk.paramsChunk;
1836+
if (chunk.done) {
1837+
console.log(`[LLM] onFunctionCallParamsChunk DONE: callIndex=${chunk.callIndex} totalLen=${_paramsChunkBufs[chunk.callIndex]?.length}`);
1838+
}
18331839
if (onToolGenerating) {
18341840
onToolGenerating({
18351841
callIndex: chunk.callIndex,

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