aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-10-23 21:22:30 +0200
committerAgando <roduccomm@gmail.com>2026-10-23 21:22:30 +0200
commitcdba58ccda05d10dd02f9dcb447f624377744b21 (patch)
treee46f4f14857b07b157c32752c38efe37528a7eee
parent64e62b6d82ed7098e5b0f1cc45d520dfe6ea3a04 (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.html17
-rw-r--r--main.html6
-rw-r--r--modules/core/core-bundle.js46
-rw-r--r--modules/core/module-aiassist.js5
-rw-r--r--modules/core/module-loader.js2
-rw-r--r--modules/core/module-world-model.js41
6 files changed, 93 insertions, 24 deletions
diff --git a/help.html b/help.html
index acffae6..5bf3543 100644
--- a/help.html
+++ b/help.html
@@ -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>
diff --git a/main.html b/main.html
index 81a87b3..41a7c82 100644
--- a/main.html
+++ b/main.html
@@ -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).