aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-10-23 18:15:05 +0200
committerAgando <roduccomm@gmail.com>2026-10-23 18:15:05 +0200
commitd100ad3c57f4a18dacb2cbc11cfdc5590f20cf08 (patch)
tree39143a7207d7b6a91963ffbcc2952a699d6d3839
parent9ae724e87a6471ce503783615ef6083d991531ce (diff)
fix(newos4): one error counter for errors/dashboard/world-model (v4.6.61)
Packet 2 (three surfaces, three answers). Measured first: `errors` scanned /logs/current.log for [ERROR] lines, but that file only ever receives explicit terminal.log('ERROR', ...) calls -- exactly one call site in the whole app -- so it read 0. The dashboard added DebugSupervisor._errorDB plus failed tool calls (18), and the world model read the debug DB alone (11). - terminal.countErrors() / countLogErrors(): the single counter, exposed on the one object all three surfaces already hold - `errors` prints the canonical total with its breakdown, the entry list (hash, count, severity, message), and reports the log scan separately as a mirror - dashboard health.errors and health.errorBreakdown derive from it - world model telemetry.recentErrors derives from it, plus errorsBySource - terminal.log('ERROR', ...) now also records into DebugSupervisor, so the log is a human-readable mirror of one store instead of a second store almost nobody wrote Verified live on v4.6.61: with a seeded debug entry + failed tool call, all three report 2; a single log('ERROR') call moves debug 1->2 and the total by exactly 1 (the mirror line is counted once, in the log, and reported separately).
-rw-r--r--help.html3
-rw-r--r--main.html6
-rw-r--r--modules/core/core-bundle.js77
-rw-r--r--modules/core/module-aiassist.js10
-rw-r--r--modules/core/module-loader.js2
-rw-r--r--modules/core/module-system.js26
-rw-r--r--modules/core/module-world-model.js9
-rw-r--r--modules/core/terminal.js32
8 files changed, 134 insertions, 31 deletions
diff --git a/help.html b/help.html
index 86816d7..60dbf4a 100644
--- a/help.html
+++ b/help.html
@@ -1672,12 +1672,13 @@ listmods</pre>
<h2>๐Ÿ“‹ Changelog</h2>
<div class="version-item">
- <div class="v-header"><span class="v-num">v4.6.57/58 โ€” October 2026</span><span class="v-date">2026-10-23</span></div>
+ <div class="v-header"><span class="v-num">v4.6.57โ€“60 โ€” October 2026</span><span class="v-date">2026-10-23</span></div>
<ul>
<li><strong>No shell โ€” and it now says so</strong> โ€” <code>;</code>, <code>&amp;&amp;</code> and <code>||</code> are refused with an explicit message instead of being folded into the command's argv (where <code>wc -l f &amp;&amp; grep -c x f</code> returned a plausible wrong number). A <code>|</code> arriving from the AI <code>run_command</code> tool is refused too, because that path bypasses the pipe splitter and degraded the pattern to empty. Documented at the top of the command reference.</li>
<li><strong>Multiple paths in <code>wc</code></strong> โ€” <code>wc -c a b</code> printed only <code>a</code> and exited clean. It now prints one line per file plus a <code>total</code>, and reports each missing file separately.</li>
<li><strong><code>ls</code> prints text</strong> โ€” it rendered only clickable chips (DOM-only), so any capture โ€” the AI <code>run_command</code> tool, the diagnostics sweep โ€” saw an empty result and reported "directory empty".</li>
<li><strong><code>view_module</code> shows what is deployed</strong> โ€” it returned the <code>_mods/&lt;Class&gt;.js</code> edit cache first, unversioned, so after a rebuild it showed pre-fix source while the bundle was new. It now fetches the deployed file and prefixes the version; the cache is a labelled fallback.</li>
+ <li><strong>One error counter</strong> โ€” <code>errors</code>, <code>get_dashboard_data</code> and the world model tallied three different sinks (0 / 18 / 11 in the same second). All three now read <code>terminal.countErrors()</code> (DebugSupervisor DB + failed tool calls); the VFS log is reported separately as a mirror, and <code>terminal.log('ERROR', โ€ฆ)</code> now records into the same DB instead of a second store nobody read.</li>
<li><strong>Predictor arithmetic</strong> โ€” the stored field named <code>mape</code> held a sum, so any JSON consumer read a mean that was <em>n</em> times too large (now <code>errorSum</code>); <code>errorPct</code> is stored raw and clamped only at render (a 475ร— miss used to be flattened to 500%); predictions are floored at 1 s and floor-hits are not scored, because one 0.079 s aborted run was rounding the next prediction to 0 s.</li>
</ul>
</div>
diff --git a/main.html b/main.html
index 5c2bf8f..ffac150 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.58"></script>
+<script src="modules/core/core-bundle.js?v=v4.6.61"></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.58"></script>
+<script src="modules/core/module-loader.js?v=v4.6.61"></script>
<!-- Critical ext bundle loads AFTER webx11 exists so registerModule calls work -->
-<script src="modules/ext/critical-ext-bundle.js?v=v4.6.58"></script>
+<script src="modules/ext/critical-ext-bundle.js?v=v4.6.61"></script>
<script>
// โ•โ•โ• ONE TRUE VERSION SOURCE โ•โ•โ•
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js
index 941707e..e265b68 100644
--- a/modules/core/core-bundle.js
+++ b/modules/core/core-bundle.js
@@ -5406,6 +5406,16 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm
}
}
log(level, source, message) {
+ // v4.6.60: an ERROR also goes to the DebugSupervisor DB, so the log is a
+ // human-readable MIRROR of one store instead of a second store that only one
+ // code path ever wrote to (packet ยง2: 0 / 18 / 11 in the same second).
+ if (String(level).toUpperCase() === 'ERROR') {
+ try {
+ const ds = this.moduleManager && this.moduleManager.modules
+ ? this.moduleManager.modules['DebugSupervisorModule'] : null;
+ if (ds && typeof ds._recordError === 'function') ds._recordError(String(message), String(source || 'terminal'));
+ } catch (e) { /* never let logging throw */ }
+ }
const d = new Date();
const ts = d.toISOString().split('T')[1].split('.')[0];
const entry = `[${ts}][${level}][${source}] ${message}`;
@@ -5424,6 +5434,28 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm
}
} catch (e) {}
}
+ // v4.6.59: ONE error counter. Three surfaces used to disagree because each
+ // tallied a different sink: `errors` scanned /logs/current.log (which only ever
+ // receives explicit terminal.log('ERROR') calls โ€” one call site in the whole app),
+ // the dashboard added debug-DB + failed tool calls, and the world model read the
+ // debug DB alone. Measured: 0 / 18 / 11 in the same second (packet ยง2).
+ countLogErrors() {
+ try {
+ const c = this.fs.readFile('/logs/current.log') || '';
+ return c.split('\n').filter(l => l.includes('[ERROR]')).length;
+ } catch (e) { return 0; }
+ }
+ countErrors() {
+ const mm = this.moduleManager;
+ const ds = mm && mm.modules ? mm.modules['DebugSupervisorModule'] : null;
+ const sm = mm && mm.modules ? mm.modules['ServerMonitorModule'] : null;
+ const debug = (ds && ds._errorDB && ds._errorDB.errors)
+ ? Object.keys(ds._errorDB.errors).length : 0;
+ const latency = (sm && Array.isArray(sm._latencyLog))
+ ? sm._latencyLog.filter(e => e && e.error).length : 0;
+ const log = this.countLogErrors();
+ return { debug: debug, latency: latency, log: log, total: debug + latency };
+ }
setAiBusy(busy) {
this._aiBusy = !!busy;
if (this._aiBusyInterval) {
@@ -5678,13 +5710,25 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm
// v4.5.43: errors [n] โ€” alias for logs [n] ERROR
this.commands['errors'] = (args) => {
const n = parseInt(args[0]) || 20;
- const content = this.terminal.fs.readFile('/logs/current.log');
- if (!content) { this.terminal.print('No logs yet', 'info'); return; }
- const lines = content.split('\n').filter(l => l && l.includes('[ERROR]'));
- const tail = lines.slice(-n);
- if (tail.length === 0) { this.terminal.print('โœ… No errors found in logs', 'success'); return; }
- this.terminal.print(`--- Errors (last ${tail.length}) ---`, 'error');
- tail.forEach(l => this.terminal.print(l, 'error'));
+ // v4.6.59: read the canonical counter (DebugSupervisor DB + failed tool
+ // calls) โ€” the same numbers the dashboard and world model publish. The VFS
+ // log is reported separately because it only receives explicit
+ // terminal.log('ERROR') calls, so it cannot be the source of truth.
+ const e = this.terminal.countErrors();
+ if (e.total === 0) {
+ this.terminal.print(`โœ… 0 errors โ€” debug DB ${e.debug} ยท failed tool calls ${e.latency} ยท log ${e.log}`, 'success');
+ } else {
+ this.terminal.print(`--- ${e.total} ${e.total === 1 ? 'error' : 'errors'} โ€” debug DB ${e.debug} ยท failed tool calls ${e.latency} ---`, 'error');
+ const ds = this.terminal.moduleManager.modules['DebugSupervisorModule'];
+ const entries = Object.entries((ds && ds._errorDB && ds._errorDB.errors) || {})
+ .map(([hash, v]) => Object.assign({ hash: hash }, v))
+ .sort((a, b) => (b.lastSeen || 0) - (a.lastSeen || 0))
+ .slice(0, n);
+ entries.forEach(x => this.terminal.print(
+ (x.hash || '').slice(0, 8) + ' ร—' + (x.count || 1) + ' ' +
+ ((x.severity || '') + ' ' + (x.message || '')).trim(), 'error'));
+ }
+ this.terminal.print(` scope: this browser session ยท log scan ${e.log} [ERROR] line(s) in /logs/current.log`, 'info');
};
// v4.5.43: doctor โ€” run all health checks, rank issues, show top 3
@@ -12914,12 +12958,13 @@ buildSystemPrompt() {
}
// System health
- var totalErrors = 0;
+ var errCounts = { total: 0, debug: 0, latency: 0, log: 0 };
try {
- var ds = mm ? mm.modules['DebugSupervisorModule'] : null;
- if (ds && ds._errorDB) totalErrors += Object.keys(ds._errorDB.errors).length;
- if (sm && sm._latencyLog) totalErrors += sm._latencyLog.filter(function(e) { return e.error; }).length;
+ if (this.terminal && typeof this.terminal.countErrors === 'function') {
+ errCounts = this.terminal.countErrors();
+ }
} catch (e) {}
+ var totalErrors = errCounts.total;
var predictor = mm ? mm.modules['PredictorModule'] : null;
var predictorAcc = 'n/a (no paired runs)';
if (predictor && predictor._accuracy && predictor._accuracy.total > 0) {
@@ -12931,6 +12976,7 @@ buildSystemPrompt() {
vfsSize: data.vfs ? data.vfs.size : '?',
online: typeof navigator !== 'undefined' ? navigator.onLine : true,
errors: totalErrors,
+ errorBreakdown: errCounts,
predictorAccuracy: predictorAcc
};
@@ -18469,10 +18515,15 @@ class WorldModelModule {
const sm = this.terminal?.moduleManager?.modules?.['ServerMonitorModule'];
const ds = this.terminal?.moduleManager?.modules?.['DebugSupervisorModule'];
const latencyDataPoints = sm?._latencyLog ? Object.keys(sm._latencyLog).length : 0;
- const recentErrors = ds ? Object.keys(ds._errorDB?.errors || {}).length : 0;
+ // v4.6.59: same canonical counter as the `errors` command and the dashboard.
+ // Reading the debug DB alone made the world model disagree with both (packet ยง2).
+ const errInfo = (this.terminal && typeof this.terminal.countErrors === 'function')
+ ? this.terminal.countErrors()
+ : { total: ds ? Object.keys(ds._errorDB?.errors || {}).length : 0, debug: 0, latency: 0, log: 0 };
return {
toolLatencyDataPoints: latencyDataPoints,
- recentErrors: recentErrors,
+ recentErrors: errInfo.total,
+ errorsBySource: { debug: errInfo.debug, toolCalls: errInfo.latency, log: errInfo.log },
};
} catch (e) {
console.warn('[WorldModel] Telemetry gather error:', e.message);
diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js
index 628e68a..3326483 100644
--- a/modules/core/module-aiassist.js
+++ b/modules/core/module-aiassist.js
@@ -4641,12 +4641,13 @@ buildSystemPrompt() {
}
// System health
- var totalErrors = 0;
+ var errCounts = { total: 0, debug: 0, latency: 0, log: 0 };
try {
- var ds = mm ? mm.modules['DebugSupervisorModule'] : null;
- if (ds && ds._errorDB) totalErrors += Object.keys(ds._errorDB.errors).length;
- if (sm && sm._latencyLog) totalErrors += sm._latencyLog.filter(function(e) { return e.error; }).length;
+ if (this.terminal && typeof this.terminal.countErrors === 'function') {
+ errCounts = this.terminal.countErrors();
+ }
} catch (e) {}
+ var totalErrors = errCounts.total;
var predictor = mm ? mm.modules['PredictorModule'] : null;
var predictorAcc = 'n/a (no paired runs)';
if (predictor && predictor._accuracy && predictor._accuracy.total > 0) {
@@ -4658,6 +4659,7 @@ buildSystemPrompt() {
vfsSize: data.vfs ? data.vfs.size : '?',
online: typeof navigator !== 'undefined' ? navigator.onLine : true,
errors: totalErrors,
+ errorBreakdown: errCounts,
predictorAccuracy: predictorAcc
};
diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js
index 0708228..ddb2874 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.58';
+ const LATEST_VER = 'v4.6.61';
const CACHE = '?v=' + LATEST_VER;
const stored = localStorage.getItem('webx11_cache_ver');
if (stored !== LATEST_VER) {
diff --git a/modules/core/module-system.js b/modules/core/module-system.js
index e7f7e5a..b9b62a6 100644
--- a/modules/core/module-system.js
+++ b/modules/core/module-system.js
@@ -144,13 +144,25 @@
// v4.5.43: errors [n] โ€” alias for logs [n] ERROR
this.commands['errors'] = (args) => {
const n = parseInt(args[0]) || 20;
- const content = this.terminal.fs.readFile('/logs/current.log');
- if (!content) { this.terminal.print('No logs yet', 'info'); return; }
- const lines = content.split('\n').filter(l => l && l.includes('[ERROR]'));
- const tail = lines.slice(-n);
- if (tail.length === 0) { this.terminal.print('โœ… No errors found in logs', 'success'); return; }
- this.terminal.print(`--- Errors (last ${tail.length}) ---`, 'error');
- tail.forEach(l => this.terminal.print(l, 'error'));
+ // v4.6.59: read the canonical counter (DebugSupervisor DB + failed tool
+ // calls) โ€” the same numbers the dashboard and world model publish. The VFS
+ // log is reported separately because it only receives explicit
+ // terminal.log('ERROR') calls, so it cannot be the source of truth.
+ const e = this.terminal.countErrors();
+ if (e.total === 0) {
+ this.terminal.print(`โœ… 0 errors โ€” debug DB ${e.debug} ยท failed tool calls ${e.latency} ยท log ${e.log}`, 'success');
+ } else {
+ this.terminal.print(`--- ${e.total} ${e.total === 1 ? 'error' : 'errors'} โ€” debug DB ${e.debug} ยท failed tool calls ${e.latency} ---`, 'error');
+ const ds = this.terminal.moduleManager.modules['DebugSupervisorModule'];
+ const entries = Object.entries((ds && ds._errorDB && ds._errorDB.errors) || {})
+ .map(([hash, v]) => Object.assign({ hash: hash }, v))
+ .sort((a, b) => (b.lastSeen || 0) - (a.lastSeen || 0))
+ .slice(0, n);
+ entries.forEach(x => this.terminal.print(
+ (x.hash || '').slice(0, 8) + ' ร—' + (x.count || 1) + ' ' +
+ ((x.severity || '') + ' ' + (x.message || '')).trim(), 'error'));
+ }
+ this.terminal.print(` scope: this browser session ยท log scan ${e.log} [ERROR] line(s) in /logs/current.log`, 'info');
};
// v4.5.43: doctor โ€” run all health checks, rank issues, show top 3
diff --git a/modules/core/module-world-model.js b/modules/core/module-world-model.js
index 4e88f59..d48327b 100644
--- a/modules/core/module-world-model.js
+++ b/modules/core/module-world-model.js
@@ -257,10 +257,15 @@ class WorldModelModule {
const sm = this.terminal?.moduleManager?.modules?.['ServerMonitorModule'];
const ds = this.terminal?.moduleManager?.modules?.['DebugSupervisorModule'];
const latencyDataPoints = sm?._latencyLog ? Object.keys(sm._latencyLog).length : 0;
- const recentErrors = ds ? Object.keys(ds._errorDB?.errors || {}).length : 0;
+ // v4.6.59: same canonical counter as the `errors` command and the dashboard.
+ // Reading the debug DB alone made the world model disagree with both (packet ยง2).
+ const errInfo = (this.terminal && typeof this.terminal.countErrors === 'function')
+ ? this.terminal.countErrors()
+ : { total: ds ? Object.keys(ds._errorDB?.errors || {}).length : 0, debug: 0, latency: 0, log: 0 };
return {
toolLatencyDataPoints: latencyDataPoints,
- recentErrors: recentErrors,
+ recentErrors: errInfo.total,
+ errorsBySource: { debug: errInfo.debug, toolCalls: errInfo.latency, log: errInfo.log },
};
} catch (e) {
console.warn('[WorldModel] Telemetry gather error:', e.message);
diff --git a/modules/core/terminal.js b/modules/core/terminal.js
index 10608d1..76406b5 100644
--- a/modules/core/terminal.js
+++ b/modules/core/terminal.js
@@ -2954,6 +2954,16 @@
}
}
log(level, source, message) {
+ // v4.6.60: an ERROR also goes to the DebugSupervisor DB, so the log is a
+ // human-readable MIRROR of one store instead of a second store that only one
+ // code path ever wrote to (packet ยง2: 0 / 18 / 11 in the same second).
+ if (String(level).toUpperCase() === 'ERROR') {
+ try {
+ const ds = this.moduleManager && this.moduleManager.modules
+ ? this.moduleManager.modules['DebugSupervisorModule'] : null;
+ if (ds && typeof ds._recordError === 'function') ds._recordError(String(message), String(source || 'terminal'));
+ } catch (e) { /* never let logging throw */ }
+ }
const d = new Date();
const ts = d.toISOString().split('T')[1].split('.')[0];
const entry = `[${ts}][${level}][${source}] ${message}`;
@@ -2972,6 +2982,28 @@
}
} catch (e) {}
}
+ // v4.6.59: ONE error counter. Three surfaces used to disagree because each
+ // tallied a different sink: `errors` scanned /logs/current.log (which only ever
+ // receives explicit terminal.log('ERROR') calls โ€” one call site in the whole app),
+ // the dashboard added debug-DB + failed tool calls, and the world model read the
+ // debug DB alone. Measured: 0 / 18 / 11 in the same second (packet ยง2).
+ countLogErrors() {
+ try {
+ const c = this.fs.readFile('/logs/current.log') || '';
+ return c.split('\n').filter(l => l.includes('[ERROR]')).length;
+ } catch (e) { return 0; }
+ }
+ countErrors() {
+ const mm = this.moduleManager;
+ const ds = mm && mm.modules ? mm.modules['DebugSupervisorModule'] : null;
+ const sm = mm && mm.modules ? mm.modules['ServerMonitorModule'] : null;
+ const debug = (ds && ds._errorDB && ds._errorDB.errors)
+ ? Object.keys(ds._errorDB.errors).length : 0;
+ const latency = (sm && Array.isArray(sm._latencyLog))
+ ? sm._latencyLog.filter(e => e && e.error).length : 0;
+ const log = this.countLogErrors();
+ return { debug: debug, latency: latency, log: log, total: debug + latency };
+ }
setAiBusy(busy) {
this._aiBusy = !!busy;
if (this._aiBusyInterval) {