aboutsummaryrefslogtreecommitdiff
path: root/modules/core/core-bundle.js
diff options
context:
space:
mode:
Diffstat (limited to 'modules/core/core-bundle.js')
-rw-r--r--modules/core/core-bundle.js92
1 files changed, 52 insertions, 40 deletions
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js
index d40988e..0d3a783 100644
--- a/modules/core/core-bundle.js
+++ b/modules/core/core-bundle.js
@@ -1604,11 +1604,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;
@@ -1775,7 +1800,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; }
@@ -1946,8 +1974,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;
@@ -10814,41 +10842,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 || '';
@@ -10888,8 +10894,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': {