From ea3acceac673b3cb6d84d38f968f057082d8ccb8 Mon Sep 17 00:00:00 2001 From: Agando Date: Tue, 27 Oct 2026 14:13:35 +0100 Subject: fix(newos4): connect to Ollama on first use, honest write_file, list_files sees /sys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three defects, each re-probed first and covered by a test that is red on HEAD. - ollama-manager.js: nothing connects at boot; only the Ollama tab button called connect(), so a user's first message died with "Ollama not connected. Use ollamaconnect first." even when the endpoint was healthy. New _ensureConnected() runs from chat(), chatStream() and chatWithTools(): one attempt, shared by concurrent callers, a "Connecting…" hint, and only then the error — which now says the endpoint is unreachable instead of telling the user to run a command they already expected to work. - module-aiassist.js write_file tool: fs.writeFile()'s false return was dropped and the tool answered "File written: …" anyway, sending the model on to a read that found nothing (MAINT-012). It now returns "Write FAILED: … — parent directory missing or target is a directory; nothing was written". - module-aiassist.js list_files tool: the hand-rolled walk over root.content never resolved the virtual mounts, so /sys — a directory the UI and `ls` both show — answered "Directory not found" (MAINT-011). It now uses fs.readDir(), which resolves mounts and returns null for a genuinely missing path. Tests: modules/test/test-ai-fs-tools.js (7 asserts) and modules/test/test-ollama-autoconnect.js (11 asserts). RED on HEAD: ai tools 4 passed/3 FAILED (/sys → "Directory not found", write_file → "File written: …"), ollama 2 passed/9 FAILED ("Use ollamaconnect first"). GREEN now 7/7 and 11/11; maintain --fix reports "node tests 9/9 pass". Live on v4.6.105: connectedBefore=false, _ensureConnected()=true in 41 ms, 37 models. --- modules/core/module-aiassist.js | 54 +++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 35 deletions(-) (limited to 'modules/core/module-aiassist.js') diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index 43cbb11..303f9c2 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -2372,41 +2372,19 @@ buildSystemPrompt() { switch (name) { case 'list_files': { const originalPath = a.path || ''; - const p = originalPath.replace(/^\//, ''); - const saved = [...this.terminal.fs.currentPath]; - if (originalPath.startsWith('/')) { - this.terminal.fs.currentPath = []; + const p = originalPath.replace(/^\//, '').replace(/\/+$/, ''); + const fsapi = this.terminal.fs; + const saved = [...fsapi.currentPath]; + if (originalPath.startsWith('/')) fsapi.currentPath = []; + // MAINT-011: readDir() resolves the virtual mounts and returns null for a + // path that does not exist. The old hand-rolled walk over root.content could + // not see /sys, so a directory the UI shows answered "Directory not found". + const entries = fsapi.readDir(p || '/'); + fsapi.currentPath = saved; + if (entries === null || entries === undefined) { + return JSON.stringify({ error: `Directory not found: ${p || '/'}` }); } - if (p) { - const parts = p.split('/'); - const dir = this.terminal.fs.root; - let cur = dir; - let found = true; - for (const part of parts) { - if (part === '..') { saved.pop(); continue; } - if (part && cur.content[part] && cur.content[part].type === 'dir') cur = cur.content[part]; - else { found = false; break; } - } - if (!found) { - // Retry from root — cwd may be stale - this.terminal.fs.currentPath = []; - cur = this.terminal.fs.root; - found = true; - for (const part of parts) { - if (part === '..') continue; - if (part && cur.content[part] && cur.content[part].type === 'dir') cur = cur.content[part]; - else { found = false; break; } - } - if (!found) { - this.terminal.fs.currentPath = saved; - return JSON.stringify({ error: `Directory not found: ${p}` }); - } - } - this.terminal.fs.currentPath = saved.concat(parts.filter(x => x !== '..')); - } - const files = this.terminal.fs.ls(); - this.terminal.fs.currentPath = saved; - return JSON.stringify(files.map(f => ({ name: f.name, type: f.type, size: f.size }))); + return JSON.stringify(entries.map(f => ({ name: f.name, type: f.type, size: f.size }))); } case 'read_file': { const rp = a.path || ''; @@ -2446,8 +2424,14 @@ buildSystemPrompt() { if (wp.startsWith('/')) this.terminal.fs.currentPath = []; const path = wp.replace(/^\//, ''); const content = a.content || ''; - this.terminal.fs.writeFile(path, content); + // MAINT-012: writeFile() returns false when the parent directory is missing + // or the target is a directory node. Ignoring it told the model the write + // succeeded, and its follow-up read then found nothing. + const wrote = this.terminal.fs.writeFile(path, content); this.terminal.fs.currentPath = savedW; + if (!wrote) { + return `Write FAILED: ${path} — parent directory missing or target is a directory; nothing was written`; + } return `File written: ${path} (${content.length} chars)`; } case 'make_directory': { -- cgit v1.2.3