diff options
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). |