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 /modules/core/module-world-model.js | |
| 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.
Diffstat (limited to 'modules/core/module-world-model.js')
| -rw-r--r-- | modules/core/module-world-model.js | 41 |
1 files changed, 32 insertions, 9 deletions
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). |