diff options
| author | Agando <roduccomm@gmail.com> | 2026-10-27 14:59:06 +0100 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-10-27 14:59:06 +0100 |
| commit | 7c0b308358d1721ad687b37dd0348c072d1d2ada (patch) | |
| tree | 103ef0598df35926e974b4fbed00e40c2f8b3cd6 /modules/core/module-memory-graph.js | |
| parent | 0ccd2cbc4cc15e0a362b08d76a8bd814abfb9ed9 (diff) | |
fix(newos4): bound one turn's tool context, honest skill-create, quarantine a corrupt memory graph
The three items the last review and probes left open, each with a test that is red on HEAD.
- MAINT-023 (module-aiassist.js): one agentic turn grew without limit — every role:'tool' body
was pushed verbatim and only this.context was trimmed BETWEEN turns (measured 6 -> 112 705
tokens across 8 model calls against maxContextTokens 12 000). Observations are now clipped to
maxToolResultChars (6 000) with a note saying how much was withheld and to re-read with
offset/limit, and _boundToolContext() elides the oldest tool bodies once the running total
passes toolContextBudgetChars (24 000), always keeping the newest 4 verbatim. The ceiling is
turn-independent: the same ~26 k chars at 8 turns and at 20.
- MAINT-024 (modules/ext/skill-pack-core.js): the command printed "✅ Skill created" with
fs.writeFile()'s boolean discarded (probe: fresh VFS, success printed, no file), stripped
invalid characters instead of rejecting the name (My-Skill -> y-kill, DevOps_Thing ->
evpshing), and treated an unreadable /path argument as the skill body. Now: kebab-case is
validated (uppercase normalises and the final name is reported; anything else is refused),
/skills is created on first use, the write and index results are checked, and an unreadable
body path is an error.
- MAINT-003 (module-memory-graph.js): a corrupt store looked exactly like a fresh install —
fsReadJSON returns null on a parse failure, so 85 nodes could vanish with no warning and no
copy. The probe confirmed the loss: seed 6 nodes, truncate nodes.json and export.json, reload
-> 1 node, no message, nothing in .trash. _quarantineCorrupt() now keeps the unreadable file
as <name>.corrupt, warns, and then loads from whatever remains.
Tests: test-tool-context-bound.js (11), test-skill-create.js (16), test-memory-graph-recovery.js
(8). RED on HEAD: tool-context crashes on _clipToolResult (exit 1), skill-create 16 assertions
fail on the missing file/index, memory-graph B1/B2/C1 fail (no warning, no .corrupt copy).
Suite now 13/13 files green; maintain --fix reports "node tests 13/13 pass".
Live on v4.6.107: skill-create My-Skill -> ✅ "my-skill"; DevOps_Thing -> Invalid skill name;
/does/not/exist.md -> Cannot read skill body; only my-skill in the index.
Diffstat (limited to 'modules/core/module-memory-graph.js')
| -rw-r--r-- | modules/core/module-memory-graph.js | 25 |
1 files changed, 25 insertions, 0 deletions
diff --git a/modules/core/module-memory-graph.js b/modules/core/module-memory-graph.js index 1cb0919..e802387 100644 --- a/modules/core/module-memory-graph.js +++ b/modules/core/module-memory-graph.js @@ -594,6 +594,12 @@ class MemoryGraphModule { try { const fs = this.terminal.fs; if (!fs) return; + // MAINT-003: a corrupt store used to look exactly like a fresh one — fsReadJSON + // returns null on a parse failure, so the graph started empty with no warning and no + // copy of what was there. Quarantine anything unreadable first, then load what's left. + this._quarantineCorrupt('export.json'); + this._quarantineCorrupt('nodes.json'); + this._quarantineCorrupt('edges.json'); // Try loading from export first const exportData = fs.fsReadJSON(`${this._indexPath}export.json`); @@ -646,6 +652,25 @@ class MemoryGraphModule { } } + // MAINT-003: keep an unreadable store aside instead of starting fresh over it. + _quarantineCorrupt(name) { + try { + const fs = this.terminal.fs; + const p = `${this._indexPath}${name}`; + const raw = fs.readFile(p); + if (typeof raw !== 'string' || !raw.length) return false; + try { JSON.parse(raw); return false; } catch (e) { + const kept = p + '.corrupt'; + fs.writeFile(kept, raw); + this.terminal.print(`⚠️ Memory graph store ${name} is unreadable — kept a copy at ${kept} and starting from what remains`, 'warning'); + if (typeof fs.deleteFile === 'function') fs.deleteFile(p); + return true; + } + } catch (e) { + return false; + } + } + _saveNow() { try { const fs = this.terminal.fs; |