From 70b9a2f8cc2b249362ca4893fdbb6703c0ea7b8a Mon Sep 17 00:00:00 2001 From: Agando Date: Thu, 22 Oct 2026 00:46:05 +0200 Subject: feat(newos4): world-model-ask answers with the LLM, grounded in the model (v4.6.50) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `world-model-ask` was a 10-keyword lookup: any real question returned "Unknown query. Try: modules, skills, ...". The documented example ("what changed since yesterday") compared nothing — it restated the current snapshot. - _modelDigest(): whitelisted snapshot (measured 942-965 chars on fixtures, ~3.3 KB live) + recent error sample + changesSinceLastSnapshot - _buildAskPrompt(): "answer only from this JSON, never invent numbers, name the command that would produce missing data" - ask(): connects Ollama on demand, answers via chat(), falls back to the facts path with a visible warning; --facts keeps the instant lookup - _diffSincePrev(): keeps one previous snapshot in /system/world-model.prev.json so "what changed" is a real diff (files 136 -> 140) - world_model_query tool returns the snapshot (digest or detail:full) instead of ten canned strings; no nested LLM call inside a tool call - help text now says what it does; node test modules/test/test-world-model-ask.js (11 assertions, runs the real class) Verified live v4.6.50: facts "Loaded 46 modules" | LLM "The snapshot does not contain information regarding the cause of memory growth ... use world-model-refresh" | diff "files 136 -> 140" | tool snapshot 3,334 bytes. --- modules/test/test-world-model-ask.js | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 modules/test/test-world-model-ask.js (limited to 'modules/test') diff --git a/modules/test/test-world-model-ask.js b/modules/test/test-world-model-ask.js new file mode 100644 index 0000000..4a0c21a --- /dev/null +++ b/modules/test/test-world-model-ask.js @@ -0,0 +1,47 @@ +// node modules/test/test-world-model-ask.js +// Executes the real class from module-world-model.js (no imports, no DOM at class level). +const fs = require('fs'); +const path = require('path'); +const src = fs.readFileSync(path.join(__dirname, '../core/module-world-model.js'), 'utf8'); +const WorldModelModule = new Function(src + '\nreturn WorldModelModule;')(); + +const wm = new WorldModelModule(); +const failures = []; +const check = (name, cond, detail) => { + if (!cond) failures.push(name + (detail ? ' — ' + detail : '')); + console.log((cond ? 'PASS' : 'FAIL'), name, detail || ''); +}; + +// no model yet +check('empty model digest is empty', wm._modelDigest() === ''); + +wm._model = { + version: 'v4.6.47', synthesized: '2026-10-21T20:00:00.000Z', + system: { health: 'ok', memory: '3 MB' }, + modules: { loaded: 30, total: 30, modifiedCount: 0, modified: [] }, + files: { total: 42, directories: 8, size: '12 KB' }, + skills: { count: 23, skills: Array.from({ length: 60 }, (_, i) => 'skill-' + i) }, + memory: { nodes: 120, edges: 200, active: true }, + agents: { agents: 4, active: 1 }, + telemetry: { toolLatencyDataPoints: 9, recentErrors: 0 }, + projects: { totalTasks: 3, activeProjects: [] }, + recent: ['Skills directory: 23 files'] +}; + +const digest = wm._modelDigest(); +check('digest is valid JSON', (() => { try { JSON.parse(digest); return true; } catch (e) { return false; } })()); +check('digest is capped at 4000', digest.length <= 4000, digest.length + ' chars'); +check('digest keeps counts', /"count":23/.test(digest) && /"loaded":30/.test(digest)); +check('digest trims the skill list to 40', (JSON.parse(digest).skills.skills || []).length === 40, + String((JSON.parse(digest).skills.skills || []).length)); + +const prompt = wm._buildAskPrompt(' why did the build break? '); +check('prompt carries the question', /QUESTION: why did the build break\?$/.test(prompt), prompt.slice(-60)); +check('prompt carries the snapshot', prompt.indexOf('SNAPSHOT: {') > 0); +check('prompt forbids invention', /Never invent numbers/.test(prompt)); + +check('facts path still answers a keyword', /30 modules/.test(wm._answerQuery('how many modules'))); +check('facts path still refuses nonsense', /Unknown query/.test(wm._answerQuery('why did it break'))); + +console.log(failures.length ? '\n' + failures.length + ' FAILED' : '\nall PASS'); +process.exit(failures.length ? 1 : 0); -- cgit v1.2.3