diff options
Diffstat (limited to 'modules/core')
| -rw-r--r-- | modules/core/core-bundle.js | 36 | ||||
| -rw-r--r-- | modules/core/module-aiassist.js | 5 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 2 | ||||
| -rw-r--r-- | modules/core/module-memory-graph.js | 31 |
4 files changed, 53 insertions, 21 deletions
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index 379c297..71f3ada 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -9708,8 +9708,9 @@ max_chars: { type: 'integer', description: 'Max characters of page content to re const lockStart = Date.now(); while (!_agentTag && this._chatLock) { if (Date.now() - lockStart > this._chatLockTimeoutMs) { - throw new Error("chat lock busy after " + Math.round(this._chatLockTimeoutMs / 1000) - + "s — refusing to release another turn's lock"); + throw new Error("AI is still busy with another turn (waited " + + Math.round(this._chatLockTimeoutMs / 1000) + + "s). Wait for it to finish, or run 'ai-stop' and retry — the running turn's lock is left alone."); } await new Promise(r => setTimeout(r, 50)); } @@ -18537,7 +18538,7 @@ class MemoryGraphModule { for (const e of edges.slice(0, 3)) { const other = e.source === n.id ? e.target : e.source; const otherLabel = (this._nodes[other] || {}).label || other; - this.terminal.print(` └─ ${e.type} → ${otherLabel} (w:${e.weight.toFixed(1)})`, 'text-muted'); + this.terminal.print(` └─ ${e.type} → ${otherLabel} (w:${this._effectiveWeight(e).toFixed(1)})`, 'text-muted'); } } if (results.length > 20) this.terminal.print(` ... and ${results.length - 20} more`, 'info'); @@ -18566,13 +18567,13 @@ class MemoryGraphModule { } // Strongest edges - const strong = [...this._edges].sort((a, b) => b.weight - a.weight).slice(0, 3); + const strong = [...this._edges].sort((a, b) => this._effectiveWeight(b) - this._effectiveWeight(a)).slice(0, 3); if (strong.length > 0) { this.terminal.print(' Strongest relationships:', 'info'); for (const e of strong) { const sLabel = (this._nodes[e.source] || {}).label || e.source; const tLabel = (this._nodes[e.target] || {}).label || e.target; - this.terminal.print(` ${sLabel} → ${tLabel} (${e.type}, w:${e.weight.toFixed(2)}, used ${e.use_count}x)`, 'info'); + this.terminal.print(` ${sLabel} → ${tLabel} (${e.type}, w:${this._effectiveWeight(e).toFixed(2)}, used ${e.use_count}x)`, 'info'); } } @@ -18617,7 +18618,7 @@ class MemoryGraphModule { if (age < THIRTY_DAYS) return true; if (e.use_count >= 3) return true; // Or if weight is high - if (e.weight > 0.8) return true; + if (this._effectiveWeight(e) > 0.8) return true; return false; }); const pruned = before - this._edges.length; @@ -18901,15 +18902,29 @@ class MemoryGraphModule { // successes — so a stored graph would keep ranking failures on top until they were re-seen. // Nothing else is rewritten: weights, ids and timestamps are otherwise left exactly as stored, // so old Date.now() task ids stay addressable (they simply coexist with new hashed ids). + // Compatibility shim for stores written before the weight rules changed. It is + // deliberately NON-DESTRUCTIVE: a load must not rewrite what the next save persists. + // Anything that needs the corrected value (ranking, pruning, display) asks + // _effectiveWeight(), so a legacy 0.8 'failed' edge stops outranking successes without + // the stored record being altered. _normalizeEdges() { for (const e of this._edges) { - if (!e.source_agents) e.source_agents = []; - const w = Number(e.weight); - e.weight = Number.isFinite(w) ? w : 0.5; - if (e.type === 'failed' && e.weight > 0.2) e.weight = 0.2; + if (!Array.isArray(e.source_agents)) e.source_agents = []; + if (!Number.isFinite(Number(e.weight))) e.weight = 0.5; } } + // The weight an edge is ranked/pruned by. Legacy stores wrote failures at 0.8 (above the + // 0.5 success weight, which put them at the top of "Strongest relationships"); the cap + // lives here instead of in the stored data. + _effectiveWeight(e) { + if (!e) return 0; + const w = Number(e.weight); + if (!Number.isFinite(w)) return 0; + if (e.type === 'failed' && w > 0.2) return 0.2; + return w; + } + // MAINT-003: keep an unreadable store aside instead of starting fresh over it. _quarantineCorrupt(name) { try { @@ -18940,6 +18955,7 @@ class MemoryGraphModule { fs.fsWriteJSON(`${this._indexPath}edges.json`, this._edges); // v4.5.89b fix: Persist decay config (P1 from review) fs.fsWriteJSON(`${this._indexPath}config.json`, { + version: 2, // format stamp: weight semantics changed in v4.6.140 decayDays: this._decayDays, decayMinUses: this._decayMinUses, lastDecayRun: this._lastDecayRun diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index db4e691..09551c2 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -833,8 +833,9 @@ max_chars: { type: 'integer', description: 'Max characters of page content to re const lockStart = Date.now(); while (!_agentTag && this._chatLock) { if (Date.now() - lockStart > this._chatLockTimeoutMs) { - throw new Error("chat lock busy after " + Math.round(this._chatLockTimeoutMs / 1000) - + "s — refusing to release another turn's lock"); + throw new Error("AI is still busy with another turn (waited " + + Math.round(this._chatLockTimeoutMs / 1000) + + "s). Wait for it to finish, or run 'ai-stop' and retry — the running turn's lock is left alone."); } await new Promise(r => setTimeout(r, 50)); } diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index 4301d0a..367ad60 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.140'; + const LATEST_VER = 'v4.6.141'; const CACHE = '?v=' + LATEST_VER; // Exposed so ext modules can register their own URL with the same cache-busting version // instead of an unversioned path the browser may already have cached (shell.js did). diff --git a/modules/core/module-memory-graph.js b/modules/core/module-memory-graph.js index e0d426e..3e52353 100644 --- a/modules/core/module-memory-graph.js +++ b/modules/core/module-memory-graph.js @@ -351,7 +351,7 @@ class MemoryGraphModule { for (const e of edges.slice(0, 3)) { const other = e.source === n.id ? e.target : e.source; const otherLabel = (this._nodes[other] || {}).label || other; - this.terminal.print(` └─ ${e.type} → ${otherLabel} (w:${e.weight.toFixed(1)})`, 'text-muted'); + this.terminal.print(` └─ ${e.type} → ${otherLabel} (w:${this._effectiveWeight(e).toFixed(1)})`, 'text-muted'); } } if (results.length > 20) this.terminal.print(` ... and ${results.length - 20} more`, 'info'); @@ -380,13 +380,13 @@ class MemoryGraphModule { } // Strongest edges - const strong = [...this._edges].sort((a, b) => b.weight - a.weight).slice(0, 3); + const strong = [...this._edges].sort((a, b) => this._effectiveWeight(b) - this._effectiveWeight(a)).slice(0, 3); if (strong.length > 0) { this.terminal.print(' Strongest relationships:', 'info'); for (const e of strong) { const sLabel = (this._nodes[e.source] || {}).label || e.source; const tLabel = (this._nodes[e.target] || {}).label || e.target; - this.terminal.print(` ${sLabel} → ${tLabel} (${e.type}, w:${e.weight.toFixed(2)}, used ${e.use_count}x)`, 'info'); + this.terminal.print(` ${sLabel} → ${tLabel} (${e.type}, w:${this._effectiveWeight(e).toFixed(2)}, used ${e.use_count}x)`, 'info'); } } @@ -431,7 +431,7 @@ class MemoryGraphModule { if (age < THIRTY_DAYS) return true; if (e.use_count >= 3) return true; // Or if weight is high - if (e.weight > 0.8) return true; + if (this._effectiveWeight(e) > 0.8) return true; return false; }); const pruned = before - this._edges.length; @@ -715,15 +715,29 @@ class MemoryGraphModule { // successes — so a stored graph would keep ranking failures on top until they were re-seen. // Nothing else is rewritten: weights, ids and timestamps are otherwise left exactly as stored, // so old Date.now() task ids stay addressable (they simply coexist with new hashed ids). + // Compatibility shim for stores written before the weight rules changed. It is + // deliberately NON-DESTRUCTIVE: a load must not rewrite what the next save persists. + // Anything that needs the corrected value (ranking, pruning, display) asks + // _effectiveWeight(), so a legacy 0.8 'failed' edge stops outranking successes without + // the stored record being altered. _normalizeEdges() { for (const e of this._edges) { - if (!e.source_agents) e.source_agents = []; - const w = Number(e.weight); - e.weight = Number.isFinite(w) ? w : 0.5; - if (e.type === 'failed' && e.weight > 0.2) e.weight = 0.2; + if (!Array.isArray(e.source_agents)) e.source_agents = []; + if (!Number.isFinite(Number(e.weight))) e.weight = 0.5; } } + // The weight an edge is ranked/pruned by. Legacy stores wrote failures at 0.8 (above the + // 0.5 success weight, which put them at the top of "Strongest relationships"); the cap + // lives here instead of in the stored data. + _effectiveWeight(e) { + if (!e) return 0; + const w = Number(e.weight); + if (!Number.isFinite(w)) return 0; + if (e.type === 'failed' && w > 0.2) return 0.2; + return w; + } + // MAINT-003: keep an unreadable store aside instead of starting fresh over it. _quarantineCorrupt(name) { try { @@ -754,6 +768,7 @@ class MemoryGraphModule { fs.fsWriteJSON(`${this._indexPath}edges.json`, this._edges); // v4.5.89b fix: Persist decay config (P1 from review) fs.fsWriteJSON(`${this._indexPath}config.json`, { + version: 2, // format stamp: weight semantics changed in v4.6.140 decayDays: this._decayDays, decayMinUses: this._decayMinUses, lastDecayRun: this._lastDecayRun |