diff options
| author | Agando <roduccomm@gmail.com> | 2026-10-27 14:13:35 +0100 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-10-27 14:13:35 +0100 |
| commit | abe6cd64666e1fa61cc79ea4392cd78339e581d8 (patch) | |
| tree | b64d0ef1516df11022969d62fffcf64937535a9a /modules/core/ollama-manager.js | |
| parent | 9a61780447066919964b66cdbaa35b9165629419 (diff) | |
fix(newos4): connect to Ollama on first use, honest write_file, list_files sees /sys
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.
Diffstat (limited to 'modules/core/ollama-manager.js')
| -rw-r--r-- | modules/core/ollama-manager.js | 46 |
1 files changed, 40 insertions, 6 deletions
diff --git a/modules/core/ollama-manager.js b/modules/core/ollama-manager.js index 4f47db2..3d157ba 100644 --- a/modules/core/ollama-manager.js +++ b/modules/core/ollama-manager.js @@ -369,11 +369,36 @@ class OllamaManager { } } + // ─── Lazy connect ──────────────────────────────────────────────────────── + /** + * Every request path calls this first. Nothing connects at boot, so the user's first message + * used to die with "Use ollamaconnect first" even when the endpoint was healthy + * (reported 2026-10-26/27). One attempt, then the caller prints the real reason. + * Concurrent callers share the same in-flight promise. + */ + async _ensureConnected() { + if (this.connected) return true; + if (this._connecting) { + try { await this._connecting; } catch (e) { /* fall through to the state check */ } + return this.connected; + } + this.terminal.print('Connecting to Ollama…', 'info'); + this._connecting = this.connect(); + try { await this._connecting; } catch (e) { + this.terminal.log('WARN', 'Ollama', 'Auto-connect failed: ' + e.message); + } + this._connecting = null; + if (!this.connected) { + this.terminal.print('Auto-connect failed — check the endpoint in the Ollama tab (or run ollamaconnect).', 'warning'); + } + return this.connected; + } + // ─── Chat (non-streaming) ──────────────────────────────────────────────── async chat(prompt, model = null) { - if (!this.connected) { - this.terminal.print('Ollama not connected. Use ollamaconnect first.', 'error'); + if (!this.connected && !(await this._ensureConnected())) { + this.terminal.print('Ollama not connected: the endpoint is unreachable. Check the Ollama tab or run ollamaconnect.', 'error'); return null; } const useModel = model || this.currentModel; @@ -540,7 +565,10 @@ class OllamaManager { // ─── Chat Stream ───────────────────────────────────────────────────────── async chatStream(prompt, onToken, onComplete, model = null) { - if (!this.connected) { this.terminal.print('Ollama not connected.', 'error'); onComplete && onComplete(null); return; } + if (!this.connected && !(await this._ensureConnected())) { + this.terminal.print('Ollama not connected: the endpoint is unreachable.', 'error'); + onComplete && onComplete(null); return; + } const useModel = model || this.currentModel; if (!useModel) { this.terminal.print('No model selected.', 'error'); onComplete && onComplete(null); return; } @@ -711,8 +739,8 @@ class OllamaManager { } async chatWithTools(messages, tools, model = null) { - if (!this.connected) { - this.terminal.print('Ollama not connected. Use ollamaconnect first.', 'error'); + if (!this.connected && !(await this._ensureConnected())) { + this.terminal.print('Ollama not connected: the endpoint is unreachable. Check the Ollama tab or run ollamaconnect.', 'error'); return { message: null, tool_calls: null, error: 'Not connected' }; } const useModel = model || this.currentModel; @@ -893,7 +921,13 @@ class OllamaManager { } return { - message: message ? { role: message.role || 'assistant', content: content } : null, + message: message ? { + role: message.role || 'assistant', + content: content, + // OpenAI-compatible providers require tool_calls INSIDE the assistant message; + // a bare { role:'tool' } with no preceding tool_calls is rejected with HTTP 400. + ...(Array.isArray(toolCalls) && toolCalls.length ? { tool_calls: toolCalls } : {}), + } : null, tool_calls: toolCalls ? toolCalls.map(tc => ({ id: tc.id, type: tc.type || 'function', |