aboutsummaryrefslogtreecommitdiff
path: root/modules/test/test-ai-fs-tools.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-10-27 14:41:20 +0100
committerAgando <roduccomm@gmail.com>2026-10-27 14:41:20 +0100
commit0ccd2cbc4cc15e0a362b08d76a8bd814abfb9ed9 (patch)
tree50020b322d79651c4e70662550b793905708a9ee /modules/test/test-ai-fs-tools.js
parentea3acceac673b3cb6d84d38f968f057082d8ccb8 (diff)
fix(newos4): name the real Ollama failure, bound auto-connect, fix the review findings
Second pass over v4.6.105, driven by an independent review that reproduced each item. - ollama-manager.js: chat() now reports the captured cause ("Ollama error: Failed to fetch", "…: HTTP 500") instead of the generic "No provider responded"; _lastChatError is reset per call so an old reason is never replayed. chatWithTools carries the reason in its error string. - ollama-manager.js: _ensureConnected() bounds connect() with a deadline (connectTimeoutMs, default 10 s). connect() uses a bare fetch(), so a blackholed endpoint previously left the shared in-flight promise pending forever and hung every later message — the old code failed fast. The attempt is now try/finally, so a throwing terminal.log() can no longer poison _connecting and suppress the retry on the next message. - ollama-manager.js: an automatic connect no longer silently rewrites the user's saved model; the previous choice is restored in memory and in localStorage, and the user is told. - ollama-manager.js: the DeepSeek empty-content branch returned top-level tool_calls with content '', so the caller pushed an assistant message with no tool_calls and then a role:'tool' reply — an orphan tool message, which is the HTTP 400 that branch exists to prevent. tool_calls now live inside the message as well. - module-aiassist.js: a no-argument list_files returned the ROOT instead of the cwd (readDir('/') instead of ls()); it now means the current directory again. Error text keeps the path as the caller wrote it, and write_file distinguishes a read-only virtual path (/sys/…) from a missing parent instead of blaming the parent. Tests: test-ollama-error-text.js (new, 14 asserts, RED 9/5 before the edit), test-ai-fs-tools.js 7 → 16 asserts (write onto a directory, /sys write, list a file, '/', no-arg cwd, relative path), test-ollama-autoconnect.js 11 → 19 (blackholed endpoint settles + retries, saved model survives; the old "connecting hint" assertion is now falsifiable — the reviewer showed it also matched connect()'s pre-existing line). Suite: 10/10 files green. Live on v4.6.106: healthy auto-connect 42 ms with the hint; blackholed endpoint settles in 1242 ms with connectTimeoutMs=1200, prints "timed out" and "unreachable", _connecting cleared.
Diffstat (limited to 'modules/test/test-ai-fs-tools.js')
-rw-r--r--modules/test/test-ai-fs-tools.js52
1 files changed, 52 insertions, 0 deletions
diff --git a/modules/test/test-ai-fs-tools.js b/modules/test/test-ai-fs-tools.js
index c427783..3968abe 100644
--- a/modules/test/test-ai-fs-tools.js
+++ b/modules/test/test-ai-fs-tools.js
@@ -65,6 +65,58 @@ const run = async (ai, name, args) => JSON.parse(await ai.runTool(name, args));
ok('D2 content landed', vfs.readFile('/missing/dir/f.txt') === 'x');
}
+ // E. write onto a directory node is refused, and the directory survives
+ {
+ const { ai, vfs } = mk();
+ vfs.mkdirRecursive('/adir');
+ vfs.writeFile('/adir/keep.txt', 'k');
+ const res = await ai.runTool('write_file', { path: '/adir', content: 'x' });
+ ok('E1 write onto a directory is refused', /Write FAILED/.test(String(res)), String(res));
+ ok('E2 directory contents intact', vfs.readFile('/adir/keep.txt') === 'k');
+ }
+
+ // F. a read-only virtual path names that as the reason
+ {
+ const { ai } = mk();
+ const res = await ai.runTool('write_file', { path: '/sys/info', content: 'x' });
+ ok('F1 virtual path reports read-only', /Write FAILED: \/sys\/info — read-only virtual path/.test(String(res)), String(res));
+ }
+
+ // G. list_files on a FILE is an error, on '/' is the root listing
+ {
+ const { ai, vfs } = mk();
+ vfs.writeFile('/file.txt', 'x');
+ const onFile = await run(ai, 'list_files', { path: '/file.txt' });
+ ok('G1 listing a file reports Directory not found', !!onFile.error && /\/file\.txt/.test(onFile.error), JSON.stringify(onFile));
+ const onRoot = await run(ai, 'list_files', { path: '/' });
+ ok('G2 root lists the virtual sys mount', Array.isArray(onRoot) && onRoot.some(e => e.name === 'sys'), JSON.stringify(onRoot).slice(0, 120));
+ }
+
+ // H. no argument means the CURRENT directory, not the root
+ {
+ const { ai, vfs } = mk();
+ vfs.mkdirRecursive('/work');
+ vfs.writeFile('/work/inside.txt', 'x');
+ vfs.writeFile('/root-only.txt', 'y');
+ vfs.currentPath = ['work'];
+ const cwd = await run(ai, 'list_files', {});
+ const names = (cwd.error ? [] : cwd.map(e => e.name));
+ ok('H1 no-arg lists the cwd', names.includes('inside.txt') && !names.includes('root-only.txt'), JSON.stringify(cwd).slice(0, 140));
+ ok('H2 cwd is restored after the call', JSON.stringify(vfs.currentPath) === '["work"]', JSON.stringify(vfs.currentPath));
+ }
+
+ // I. a relative path resolves from the cwd
+ {
+ const { ai, vfs } = mk();
+ vfs.mkdirRecursive('/docs');
+ vfs.writeFile('/docs/sub.txt', 'x');
+ vfs.currentPath = ['docs'];
+ const res = await run(ai, 'list_files', { path: 'sub.txt' });
+ ok('I1 a relative file path is resolved, not guessed', !!res.error, JSON.stringify(res));
+ const dirRes = await run(ai, 'list_files', { path: '.' });
+ ok('I2 "." lists the cwd', Array.isArray(dirRes) && dirRes.some(e => e.name === 'sub.txt'), JSON.stringify(dirRes).slice(0, 120));
+ }
+
if (fails === 0) {
console.log('test-ai-fs-tools: ' + passes + ' passed, 0 failed');
} else {