diff options
| -rw-r--r-- | help.html | 3 | ||||
| -rw-r--r-- | main.html | 6 | ||||
| -rw-r--r-- | modules/core/core-bundle.js | 77 | ||||
| -rw-r--r-- | modules/core/module-aiassist.js | 10 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 2 | ||||
| -rw-r--r-- | modules/core/module-system.js | 26 | ||||
| -rw-r--r-- | modules/core/module-world-model.js | 9 | ||||
| -rw-r--r-- | modules/core/terminal.js | 32 |
8 files changed, 134 insertions, 31 deletions
@@ -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>&&</code> and <code>||</code> are refused with an explicit message instead of being folded into the command's argv (where <code>wc -l f && 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/<Class>.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> @@ -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) { |