diff options
| author | Agando <roduccomm@gmail.com> | 2026-10-23 21:22:30 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-10-23 21:22:30 +0200 |
| commit | cdba58ccda05d10dd02f9dcb447f624377744b21 (patch) | |
| tree | e46f4f14857b07b157c32752c38efe37528a7eee | |
| parent | 64e62b6d82ed7098e5b0f1cc45d520dfe6ea3a04 (diff) | |
fix(newos4): world_model_query default path parses again (v4.6.83-84)
_modelDigest() returned JSON.stringify(...) cut at 4000 chars plus '…(digest truncated)'. The
tool then did JSON.parse() on it, so the default detail threw 'Unexpected end of JSON input'
every single call; detail:'full' worked only because it returned the object untouched.
The digest now returns an OBJECT (_modelSnapshot) and shrinks by reducing list lengths, then by
dropping whole sections with an explicit 'omitted' list. The string form is generated at the
edge and is always valid JSON.
Verified: default call -> no error, 13-key snapshot, facts answered; caps 400/1200/4000 all under
cap and parseable; the residual floor at cap 200 is identity fields only.
| -rw-r--r-- | help.html | 17 | ||||
| -rw-r--r-- | main.html | 6 | ||||
| -rw-r--r-- | modules/core/core-bundle.js | 46 | ||||
| -rw-r--r-- | modules/core/module-aiassist.js | 5 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 2 | ||||
| -rw-r--r-- | modules/core/module-world-model.js | 41 |
6 files changed, 93 insertions, 24 deletions
@@ -1689,6 +1689,23 @@ listmods</pre> <h2>📋 Changelog</h2> <div class="version-item"> + <div class="v-header"><span class="v-num">v4.6.83–84 — October 2026</span> + <span class="v-date">world_model_query</span></div> + <ul> + <li><strong>The first tool the system prompt tells the model to call no longer fails.</strong> + <code>world_model_query</code> with the default detail parsed + <code>_modelDigest()</code> as JSON — but that digest was a JSON <em>string</em> cut at 4000 + characters with <code>…(digest truncated)</code> glued on, so it threw + <code>Unexpected end of JSON input</code> every time. <code>detail:"full"</code> worked only + because it returned the object and never parsed anything.</li> + <li><strong>A digest shrinks, it does not break.</strong> The model now builds a real + snapshot object and drops list lengths (40 → 20 → 10 → 5 → 3 → 1 → 0), and if still too + large drops whole sections, reporting what it left out in <code>omitted</code>. The string + form is only produced at the edge, and is always parseable.</li> + </ul> + </div> + + <div class="version-item"> <div class="v-header"><span class="v-num">v4.6.82 — October 2026</span> <span class="v-date">skill-create parser</span></div> <ul> @@ -1349,13 +1349,13 @@ </script> <!-- v4.5.63: A2A scratchpad fixes + agent-register arg fix --> -<script src="modules/core/core-bundle.js?v=v4.6.82"></script> +<script src="modules/core/core-bundle.js?v=v4.6.84"></script> <!-- Async dependency-ordered module loader now only initializes terminal and loads optional ext modules --> -<script src="modules/core/module-loader.js?v=v4.6.82"></script> +<script src="modules/core/module-loader.js?v=v4.6.84"></script> <!-- Critical ext bundle loads AFTER webx11 exists so registerModule calls work --> -<script src="modules/ext/critical-ext-bundle.js?v=v4.6.82"></script> +<script src="modules/ext/critical-ext-bundle.js?v=v4.6.84"></script> <script> // ═══ ONE TRUE VERSION SOURCE ═══ diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index b565365..0b2795a 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -11557,7 +11557,10 @@ buildSystemPrompt() { facts: wm._answerQuery(a.question || ''), version: wm._model.version, synthesized: wm._model.synthesized, - snapshot: (a.detail === 'full') ? wm._model : JSON.parse(wm._modelDigest()) + // v4.6.83: this was JSON.parse(wm._modelDigest()) and the digest was a + // string truncated mid-token, so the default path always threw + // "Unexpected end of JSON input". The model now hands back an object. + snapshot: (a.detail === 'full') ? wm._model : wm._modelSnapshot() }; return JSON.stringify(wmOut); } @@ -19051,30 +19054,53 @@ class WorldModelModule { // 2026-10-21: ask() used to be a keyword table only, so any real question got // "Unknown query". Whitelist the fields we are willing to send to a model // (never the raw model object: it is persisted alongside other VFS data). - _modelDigest(maxChars) { + // v4.6.83: the digest used to be a JSON string cut at 4000 chars with '…(digest truncated)' + // glued on. Every caller that tried to parse it got "Unexpected end of JSON input" — the + // world_model_query tool did exactly that on its default path. A digest must shrink, never + // break: this returns a valid OBJECT and the string form is only produced at the edge. + _modelSnapshot(maxChars) { const m = this._model; - if (!m) return ''; + if (!m) return null; const cap = maxChars || 4000; - const pick = { + const build = (keep) => ({ version: m.version, synthesized: m.synthesized, system: m.system, modules: m.modules, files: m.files, - skills: { count: m.skills && m.skills.count, skills: ((m.skills && m.skills.skills) || []).slice(0, 40) }, + skills: { count: m.skills && m.skills.count, skills: ((m.skills && m.skills.skills) || []).slice(0, keep) }, memory: m.memory, agents: m.agents, telemetry: m.telemetry, projects: m.projects, - recent: (m.recent || []).slice(0, 20), + recent: (m.recent || []).slice(0, Math.min(keep, 20)), // 2026-10-21: the model only ever exposed a COUNT ("9 recent errors") with no detail, // so neither the user nor the LLM could say what failed. Carry the last few. recentErrorSample: this._errorSample ? this._errorSample() : [], changesSinceLastSnapshot: this._diffSincePrev ? this._diffSincePrev() : [] - }; - let json = JSON.stringify(pick); - if (json.length > cap) json = json.slice(0, cap) + '…(digest truncated)'; - return json; + }); + // Shrink the lists until the snapshot fits. Truncating the JSON text is what broke it. + for (const keep of [40, 20, 10, 5, 3, 1, 0]) { + const pick = build(keep); + if (JSON.stringify(pick).length <= cap) return pick; + } + // Last resort: drop whole sections, in order of least value, until it fits. Still a + // valid object — a caller can always JSON.parse it and see what was left out. + const lean = build(0); + lean.truncated = true; + lean.omitted = []; + for (const key of ['changesSinceLastSnapshot', 'recentErrorSample', 'recent', 'projects', + 'skills', 'agents', 'files', 'modules', 'telemetry', 'memory']) { + if (JSON.stringify(lean).length <= cap) break; + if (key in lean) { delete lean[key]; lean.omitted.push(key); } + } + return lean; + } + + // String form, for prompts. Always valid JSON now. + _modelDigest(maxChars) { + const snap = this._modelSnapshot(maxChars); + return snap ? JSON.stringify(snap) : ''; } // Last few tool/agent errors with their tool name, for the digest (bounded). diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index c3ebe1f..55dde6f 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -3170,7 +3170,10 @@ buildSystemPrompt() { facts: wm._answerQuery(a.question || ''), version: wm._model.version, synthesized: wm._model.synthesized, - snapshot: (a.detail === 'full') ? wm._model : JSON.parse(wm._modelDigest()) + // v4.6.83: this was JSON.parse(wm._modelDigest()) and the digest was a + // string truncated mid-token, so the default path always threw + // "Unexpected end of JSON input". The model now hands back an object. + snapshot: (a.detail === 'full') ? wm._model : wm._modelSnapshot() }; return JSON.stringify(wmOut); } diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index 626d7bc..9a373a6 100644 --- a/modules/core/module-loader.js +++ b/modules/core/module-loader.js @@ -4,7 +4,7 @@ // Core module bundle loaded synchronously in main.html. // This loader only handles version cache-busting, splash, terminal init, and optional ext modules. - const LATEST_VER = 'v4.6.82'; + const LATEST_VER = 'v4.6.84'; const CACHE = '?v=' + LATEST_VER; const stored = localStorage.getItem('webx11_cache_ver'); if (stored !== LATEST_VER) { diff --git a/modules/core/module-world-model.js b/modules/core/module-world-model.js index d48327b..54a17e4 100644 --- a/modules/core/module-world-model.js +++ b/modules/core/module-world-model.js @@ -446,30 +446,53 @@ class WorldModelModule { // 2026-10-21: ask() used to be a keyword table only, so any real question got // "Unknown query". Whitelist the fields we are willing to send to a model // (never the raw model object: it is persisted alongside other VFS data). - _modelDigest(maxChars) { + // v4.6.83: the digest used to be a JSON string cut at 4000 chars with '…(digest truncated)' + // glued on. Every caller that tried to parse it got "Unexpected end of JSON input" — the + // world_model_query tool did exactly that on its default path. A digest must shrink, never + // break: this returns a valid OBJECT and the string form is only produced at the edge. + _modelSnapshot(maxChars) { const m = this._model; - if (!m) return ''; + if (!m) return null; const cap = maxChars || 4000; - const pick = { + const build = (keep) => ({ version: m.version, synthesized: m.synthesized, system: m.system, modules: m.modules, files: m.files, - skills: { count: m.skills && m.skills.count, skills: ((m.skills && m.skills.skills) || []).slice(0, 40) }, + skills: { count: m.skills && m.skills.count, skills: ((m.skills && m.skills.skills) || []).slice(0, keep) }, memory: m.memory, agents: m.agents, telemetry: m.telemetry, projects: m.projects, - recent: (m.recent || []).slice(0, 20), + recent: (m.recent || []).slice(0, Math.min(keep, 20)), // 2026-10-21: the model only ever exposed a COUNT ("9 recent errors") with no detail, // so neither the user nor the LLM could say what failed. Carry the last few. recentErrorSample: this._errorSample ? this._errorSample() : [], changesSinceLastSnapshot: this._diffSincePrev ? this._diffSincePrev() : [] - }; - let json = JSON.stringify(pick); - if (json.length > cap) json = json.slice(0, cap) + '…(digest truncated)'; - return json; + }); + // Shrink the lists until the snapshot fits. Truncating the JSON text is what broke it. + for (const keep of [40, 20, 10, 5, 3, 1, 0]) { + const pick = build(keep); + if (JSON.stringify(pick).length <= cap) return pick; + } + // Last resort: drop whole sections, in order of least value, until it fits. Still a + // valid object — a caller can always JSON.parse it and see what was left out. + const lean = build(0); + lean.truncated = true; + lean.omitted = []; + for (const key of ['changesSinceLastSnapshot', 'recentErrorSample', 'recent', 'projects', + 'skills', 'agents', 'files', 'modules', 'telemetry', 'memory']) { + if (JSON.stringify(lean).length <= cap) break; + if (key in lean) { delete lean[key]; lean.omitted.push(key); } + } + return lean; + } + + // String form, for prompts. Always valid JSON now. + _modelDigest(maxChars) { + const snap = this._modelSnapshot(maxChars); + return snap ? JSON.stringify(snap) : ''; } // Last few tool/agent errors with their tool name, for the digest (bounded). |