aboutsummaryrefslogtreecommitdiff
path: root/modules/core/core-bundle.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-10-27 17:08:42 +0100
committerAgando <roduccomm@gmail.com>2026-10-27 17:08:42 +0100
commitd2588d521b304d0141cfba1f5c8dd4ec50492097 (patch)
treec88017dd6d716d4ab04dd1494e418cd09ec47c37 /modules/core/core-bundle.js
parentb7b0c3481760315ba49f5db1a7e61079b316f8af (diff)
fix(newos4): send num_ctx to Ollama, and make one auto-connect failure read as one line
Two things I had flagged as "worth your attention" and one leftover nit. - num_ctx was never sent. Ollama's server-side default context is 2k-4k depending on the build, so a system prompt + 20 messages + up to 24k chars of tool results could be truncated server-side, which is exactly the "it forgot what it just read" class of report. Every Ollama request now carries options.num_ctx (default 16384, overridable via webx11_ollama_numctx, floor 512); the OpenAI-compatible branch is untouched because num_ctx is not one of its parameters. - Automatic connect prints one outcome instead of one line per endpoint tried (7 -> 3 on a dead endpoint). Interactive `ollamaconnect` keeps the per-endpoint detail. - write_file's failure message no longer echoes a trailing slash. Verification: - test-ollama-numctx.js (7 asserts): generate and chat payloads carry num_ctx=16384, a stored override is used, junk/too-small values fall back, and the /v1/chat/completions branch does not receive it. RED on HEAD: TypeError reading 'num_ctx' of undefined. Suite 20/20. - Live v4.6.117, capturing the real request: {"num_ctx":16384} on /api/generate with model gemma4:31b-cloud, reply "OK". - Live v4.6.118, dead endpoint (127.0.0.1:9, fallback disabled): automatic -> 3 lines ("connecting on first use…", "Connecting to dead…", "Auto-connect failed — …"); manual connect() -> 4 lines including "endpoint http://127.0.0.1:9 failed", so the suppression is conditional, not blanket.
Diffstat (limited to 'modules/core/core-bundle.js')
-rw-r--r--modules/core/core-bundle.js38
1 files changed, 28 insertions, 10 deletions
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js
index 1a92ef9..fcf1505 100644
--- a/modules/core/core-bundle.js
+++ b/modules/core/core-bundle.js
@@ -1290,6 +1290,19 @@ class OllamaManager {
// ─── Provider management ─────────────────────────────────────────────────
+ // Ollama's server-side default context is small (2k-4k depending on the build). The agent
+ // sends a system prompt, up to 20 messages and up to 24k chars of tool results, so without an
+ // explicit num_ctx the server silently truncates the prompt — the model "forgets" what it was
+ // just told. Send it on every Ollama request; the OpenAI-compatible branch has its own params.
+ _ctxTokens() {
+ const n = Number(this.numCtx || (typeof localStorage !== 'undefined' ? localStorage.getItem('webx11_ollama_numctx') : 0));
+ return Number.isFinite(n) && n >= 512 ? Math.floor(n) : 16384;
+ }
+
+ _ollamaOptions(extra) {
+ return Object.assign({ num_ctx: this._ctxTokens() }, extra || {});
+ }
+
_loadProviders() {
const stored = this._getSecret('webx11_llm_providers');
if (stored) {
@@ -1506,7 +1519,7 @@ class OllamaManager {
break;
} catch (error) {
this.terminal.log('WARN', 'Ollama', provider.name + ' endpoint ' + ep + ' failed: ' + error.message);
- this.terminal.print(`${provider.name} endpoint ${ep} failed: ${error.message}`, 'warning');
+ if (!this._quietConnect) this.terminal.print(`${provider.name} endpoint ${ep} failed: ${error.message}`, 'warning');
}
}
} else {
@@ -1544,7 +1557,7 @@ class OllamaManager {
success = true;
} catch (error) {
this.terminal.log('WARN', 'Ollama', provider.name + ' failed: ' + error.message);
- this.terminal.print(`${provider.name} failed: ${error.message}`, 'warning');
+ if (!this._quietConnect) this.terminal.print(`${provider.name} failed: ${error.message}`, 'warning');
}
}
@@ -1554,7 +1567,7 @@ class OllamaManager {
// If provider-based connect failed, try bare endpoint fallback (legacy)
for (const ep of allEndpoints) {
try {
- this.terminal.print(`Connecting (legacy fallback) to ${ep}...`, 'info');
+ if (!this._quietConnect) this.terminal.print(`Connecting (legacy fallback) to ${ep}...`, 'info');
const response = await fetch(`${ep}/api/tags`, {
method: 'GET', headers: { 'Content-Type': 'application/json' }
});
@@ -1580,7 +1593,7 @@ class OllamaManager {
return true;
} catch (error) {
this.terminal.log('WARN', 'Ollama', 'Endpoint ' + ep + ' failed: ' + error.message);
- this.terminal.print(`Endpoint ${ep} failed: ${error.message}`, 'warning');
+ if (!this._quietConnect) this.terminal.print(`Endpoint ${ep} failed: ${error.message}`, 'warning');
}
}
@@ -1622,6 +1635,10 @@ class OllamaManager {
this.terminal.print('Ollama: connecting on first use…', 'info');
let timedOut = false;
try {
+ // One failure should read as one failure: connect() prints a line per endpoint it
+ // tries, which made an auto-connect failure seven lines of noise. Keep the detail in
+ // the log, print only the outcome for an automatic attempt.
+ this._quietConnect = true;
this._connecting = this.connect();
// connect() uses a bare fetch(), so a blackholed endpoint would hang this promise
// forever and — because the promise is shared — every later message with it.
@@ -1635,6 +1652,7 @@ class OllamaManager {
// A throwing terminal.log() must not leave _connecting poisoned: log defensively.
try { this.terminal.log('WARN', 'Ollama', 'Auto-connect failed: ' + (e && e.message ? e.message : e)); } catch (_) {}
} finally {
+ this._quietConnect = false;
this._connecting = null;
}
if (this.connected && savedModel && !(this.models || []).some(m => m.name === savedModel)) {
@@ -1706,7 +1724,7 @@ class OllamaManager {
const response = await fetch(`${provider.endpoint}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ model: useModel, prompt: prompt, stream: this.streamEnabled }),
+ body: JSON.stringify({ model: useModel, prompt: prompt, stream: this.streamEnabled, options: this._ollamaOptions() }),
signal
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
@@ -1876,7 +1894,7 @@ class OllamaManager {
const response = await fetch(`${provider.endpoint}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ model: useModel, prompt: prompt, stream: this.streamEnabled }),
+ body: JSON.stringify({ model: useModel, prompt: prompt, stream: this.streamEnabled, options: this._ollamaOptions() }),
signal
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
@@ -2053,7 +2071,7 @@ class OllamaManager {
try {
if (provider.type === 'ollama') {
- const body = { model: useModel, messages, stream: false };
+ const body = { model: useModel, messages, stream: false, options: this._ollamaOptions() };
if (tools && tools.length > 0) body.tools = tools;
const t0 = Date.now();
const response = await fetch(`${provider.endpoint}/api/chat`, {
@@ -2286,7 +2304,7 @@ ${toolDesc}
Only output XML function calls for tool tasks. If no tool is needed, answer normally.` };
const msgsWithTools = [toolContext, ...messages];
- const body = { model: useModel, messages: msgsWithTools, stream: false, options: { temperature: 0.1 } };
+ const body = { model: useModel, messages: msgsWithTools, stream: false, options: this._ollamaOptions({ temperature: 0.1 }) };
const response = await fetch(`${this.endpoint}/api/chat`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
@@ -2337,7 +2355,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm
// Original Ollama fallback
this.terminal.print(`Model doesn't support tool calling. Retrying /api/chat without tools...`, 'warning');
try {
- const body = { model: useModel, messages, stream: false };
+ const body = { model: useModel, messages, stream: false, options: this._ollamaOptions() };
const response = await fetch(`${this.endpoint}/api/chat`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
@@ -11084,7 +11102,7 @@ buildSystemPrompt() {
const why = (typeof this.terminal.fs._isVirtualPath === 'function' && this.terminal.fs._isVirtualPath(wp))
? 'read-only virtual path'
: 'parent directory missing or target is a directory';
- return `Write FAILED: ${wp} — ${why}; nothing was written`;
+ return `Write FAILED: ${String(wp).replace(/\/+$/, '') || '/'} — ${why}; nothing was written`;
}
return `File written: ${wp} (${content.length} chars)`;
}