aboutsummaryrefslogtreecommitdiff
path: root/modules/core/core-bundle.js
diff options
context:
space:
mode:
Diffstat (limited to 'modules/core/core-bundle.js')
-rw-r--r--modules/core/core-bundle.js152
1 files changed, 144 insertions, 8 deletions
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js
index ddcac45..fe86c8f 100644
--- a/modules/core/core-bundle.js
+++ b/modules/core/core-bundle.js
@@ -14297,9 +14297,45 @@ maxFetchChars: this.maxFetchChars,
}
};
walk('/');
- data.vfs = { files: fileCount, dirs: dirCount, size: (JSON.stringify(fs.root || {}).length / 1024).toFixed(1) + 'KB' };
+ // Stored bytes: the UTF-8 length of each FILE's content, summed. The
+ // old JSON.stringify(fs.root||{}).length counted UTF-16 code units
+ // plus the serialisation structure (braces, quotes, keys, commas) —
+ // ~2x for plain ASCII and wrong the other way for non-ASCII. This is
+ // the unit `vfs-size` and `doctor` report.
+ var u8 = function(s) {
+ if (typeof s !== 'string' || s.length === 0) return 0;
+ if (typeof Blob !== 'undefined') { try { return new Blob([s]).size; } catch (e) {} }
+ if (typeof TextEncoder !== 'undefined') { try { return new TextEncoder().encode(s).length; } catch (e) {} }
+ var n = 0;
+ for (var j = 0; j < s.length; j++) {
+ var cp = s.codePointAt(j);
+ if (cp > 0xffff) j++; // surrogate pair consumed
+ n += cp < 0x80 ? 1 : cp < 0x800 ? 2 : cp < 0x10000 ? 3 : 4;
+ }
+ return n;
+ };
+ var measure = function(node, depth) {
+ var out = { bytes: 0, files: 0 };
+ if (!node || !node.content || depth > 64) return out;
+ for (var k in node.content) {
+ if (!Object.prototype.hasOwnProperty.call(node.content, k)) continue;
+ var c = node.content[k];
+ if (!c || typeof c !== 'object') continue;
+ if (c.type === 'file') { out.bytes += u8(c.content); out.files++; }
+ else { var sub = measure(c, depth + 1); out.bytes += sub.bytes; out.files += sub.files; }
+ }
+ return out;
+ };
+ var vfsB = measure(fs.root, 0);
+ data.vfs = {
+ files: fileCount, dirs: dirCount,
+ sizeBytes: vfsB.bytes,
+ size: vfsB.bytes < 1024 ? vfsB.bytes + ' B'
+ : vfsB.bytes < 1048576 ? (vfsB.bytes / 1024).toFixed(1) + ' KB'
+ : (vfsB.bytes / 1048576).toFixed(2) + ' MB'
+ };
}
- } catch (e) { data.vfs = { files: 0, dirs: 0, size: '0KB' }; }
+ } catch (e) { data.vfs = { files: 0, dirs: 0, size: '0 B', sizeBytes: 0 }; }
// Ollama
data.ollama = {
@@ -14359,7 +14395,15 @@ maxFetchChars: this.maxFetchChars,
data.health = {
modules: data.modules.loaded + '/' + data.modules.total,
vfsSize: data.vfs ? data.vfs.size : '?',
- online: typeof navigator !== 'undefined' ? navigator.onLine : true,
+ vfsSizeBytes: data.vfs && typeof data.vfs.sizeBytes === 'number' ? data.vfs.sizeBytes : null,
+ // NO `online` boolean. `navigator.onLine` reports false on a fully
+ // connected Android WebView, which is how the debug panel and the
+ // dashboard told working users they were offline. `doctor` owns the only
+ // reachability verdict; SystemModule exposes no last verdict, so until one
+ // is probed the honest value is 'unknown'.
+ network: 'unknown',
+ networkKnown: false,
+ networkHint: 'run doctor for a live probe',
errors: totalErrors,
errorBreakdown: errCounts,
predictorAccuracy: predictorAcc
@@ -19954,6 +19998,10 @@ class PredictorModule {
async _pushToServer() {
const cfg = this._wdConfig();
if (!cfg) { this._storeMode = 'cache-only (no WebDAV config)'; return false; }
+ // The store is a /spaces/<user>/_data path, i.e. auth-gated. A config without credentials
+ // produces a guaranteed 401 on every boot (measured: 107 × 401 on predictor.json in one day)
+ // and that 401 is what the console reports as an error. No credentials, no request.
+ if (!cfg.user || !cfg.pass) { this._storeMode = 'cache-only (no credentials)'; return false; }
try {
const body = this.terminal.fs.readFile('/system/metrics/predictor.json') || '{}';
const put = () => fetch(this._storeUrl(cfg), { method: 'PUT', headers: this._storeHeaders(cfg), body: body });
@@ -19976,6 +20024,9 @@ class PredictorModule {
async _pullFromServer(force) {
const cfg = this._wdConfig();
if (!cfg) { this._storeMode = 'cache-only (no WebDAV config)'; return false; }
+ // Same rule as the push: an auth-gated path read without credentials is a 401, not a
+ // "server (empty)" answer, and it costs a console error on every page load.
+ if (!cfg.user || !cfg.pass) { this._storeMode = 'cache-only (no credentials)'; return false; }
if (!force && this._cacheStamp && (Date.now() - this._cacheStamp) < this._STORE_TTL_MS) return false;
try {
const resp = await fetch(this._storeUrl(cfg), { headers: this._storeHeaders(cfg) });
@@ -20173,6 +20224,65 @@ class WorldModelModule {
}
}
+ // ─── Measurement helpers ───
+
+ // UTF-8 byte length of a string. `JSON.stringify(root).length` — the unit vfsSize used to
+ // report — is UTF-16 string units PLUS every structural character of the serialisation
+ // (braces, quotes, keys, commas): it measured 1961 for a 1046-byte tree, and counts 'é' as 1.
+ // Blob is exact in the browser and in Node 22; TextEncoder is the fallback, then a manual
+ // code-point count (never `s.length`, which would reintroduce the UTF-16 lie).
+ _utf8Len(s) {
+ if (typeof s !== 'string' || s.length === 0) return 0;
+ if (typeof Blob !== 'undefined') {
+ try { return new Blob([s]).size; } catch (e) { /* fall through */ }
+ }
+ if (typeof TextEncoder !== 'undefined') {
+ try { return new TextEncoder().encode(s).length; } catch (e) { /* fall through */ }
+ }
+ let n = 0;
+ for (let i = 0; i < s.length; i++) {
+ const cp = s.codePointAt(i);
+ if (cp > 0xffff) i++; // surrogate pair consumed
+ n += cp < 0x80 ? 1 : cp < 0x800 ? 2 : cp < 0x10000 ? 3 : 4;
+ }
+ return n;
+ }
+
+ // Stored bytes of a VFS subtree: the UTF-8 length of each FILE's content, summed. A directory
+ // node contributes nothing of its own, so a nested tree is never double counted. This is the
+ // same unit `vfs-size` and `doctor` report. Depth- and node-capped so a malformed or cyclic
+ // tree cannot hang synthesis.
+ _measureVfs(root) {
+ const out = { bytes: 0, files: 0 };
+ let nodes = 0;
+ const walk = (node, depth) => {
+ if (!node || typeof node !== 'object' || depth > 64) return;
+ const kids = node.content;
+ if (!kids || typeof kids !== 'object') return;
+ for (const name of Object.keys(kids)) {
+ if (++nodes > 20000) return;
+ const child = kids[name];
+ if (!child || typeof child !== 'object') continue;
+ if (child.type === 'file') {
+ out.bytes += this._utf8Len(child.content);
+ out.files += 1;
+ } else {
+ walk(child, depth + 1);
+ }
+ }
+ };
+ walk(root, 0);
+ return out;
+ }
+
+ // Human form for display; the exact byte count is printed beside it so the rows reconcile.
+ _fmtBytes(bytes) {
+ if (typeof bytes !== 'number' || !isFinite(bytes)) return '?';
+ if (bytes < 1024) return bytes + ' B';
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
+ return (bytes / 1024 / 1024).toFixed(2) + ' MB';
+ }
+
_gatherSystemInfo(fs, moduleCount, version) {
// Get real health data from ServerMonitorModule's _latencyLog and DebugSupervisorModule
const sm = this.terminal?.moduleManager?.modules?.['ServerMonitorModule'];
@@ -20180,11 +20290,18 @@ class WorldModelModule {
const errorCount = ds ? Object.keys(ds._errorDB?.errors || {}).length : 0;
const latencyErrors = sm?._latencyLog ? Object.values(sm._latencyLog).filter(e => e.status === 'error').length : 0;
const totalErrors = errorCount + latencyErrors;
+ const vfs = this._measureVfs(fs?.root || null);
return {
version: `newOS4 ${version}`,
modules: moduleCount || Object.keys(this.terminal?.moduleManager?.modules || {}).length || 0,
- vfsSize: JSON.stringify(fs?.root || {}).length,
- online: typeof navigator !== 'undefined' ? navigator.onLine : true,
+ // Stored bytes of file content — the one unit `vfs-size` and `doctor` report.
+ vfsSize: vfs.bytes,
+ vfsFiles: vfs.files,
+ // No reachability verdict is manufactured here. `navigator.onLine` reports false on a
+ // fully connected Android WebView, and `doctor` is the only place that probes. Until
+ // SystemModule exposes a last-doctor verdict, 'unknown' is the honest value.
+ network: 'unknown',
+ networkKnown: false,
memory: typeof performance !== 'undefined' && performance.memory
? `${(performance.memory.usedJSHeapSize / 1048576).toFixed(1)}MB`
: 'N/A',
@@ -20217,7 +20334,7 @@ class WorldModelModule {
}
_gatherFileInfo(fs) {
- if (!fs) return { total: 0, size: '0KB' };
+ if (!fs) return { total: 0, size: '0 B', sizeBytes: 0 };
let fileCount = 0, dirCount = 0;
const walk = (path) => {
const entries = fs.ls(path);
@@ -20233,10 +20350,14 @@ class WorldModelModule {
}
};
walk('/');
+ // Same unit as system.vfsSize — the two are printed on adjacent lines, so they must
+ // reconcile. Stored UTF-8 bytes of file content, not JSON.stringify(fs.root).length.
+ const vfs = this._measureVfs(fs.root);
return {
total: fileCount,
directories: dirCount,
- size: `${(JSON.stringify(fs.root).length / 1024).toFixed(1)}KB`
+ size: this._fmtBytes(vfs.bytes),
+ sizeBytes: vfs.bytes
};
}
@@ -20372,7 +20493,14 @@ class WorldModelModule {
this.terminal.print('🌐 World Model', 'header');
this.terminal.print(` Version: ${m.version} (last synthsized: ${new Date(m.synthesized).toLocaleTimeString()})`, 'info');
- this.terminal.print(` ⚙️ System — ${m.system.modules} modules loaded, ${m.system.vfsSize} VFS, ${m.system.memory}`, 'info');
+ // Tolerate a model loaded from VFS that predates the byte fix (vfsSize was a "1.9KB"
+ // string then): render whatever shape is there instead of "? B".
+ const vfsLine = typeof m.system.vfsSize === 'number'
+ ? (m.system.vfsSize < 1024
+ ? `${m.system.vfsSize} bytes`
+ : `${this._fmtBytes(m.system.vfsSize)} (${m.system.vfsSize} bytes)`)
+ : String(m.system.vfsSize);
+ this.terminal.print(` ⚙️ System — ${m.system.modules} modules loaded, ${vfsLine} VFS, ${m.system.memory}`, 'info');
this.terminal.print(` 🧩 Modules — ${m.modules.loaded} loaded (${m.modules.modifiedCount} modified)`, 'info');
this.terminal.print(` 📁 Files — ${m.files.total} files, ${m.files.directories} dirs, ${m.files.size}`, 'info');
this.terminal.print(` 📚 Skills — ${m.skills.count} installed`, 'info');
@@ -20616,6 +20744,14 @@ class WorldModelModule {
if (!fs) return;
const data = fs.fsReadJSON('/system/world-model.json');
if (data) {
+ // A model persisted by a pre-v4.6.161 build carries `system.online` — a
+ // navigator.onLine boolean the digest would hand to the LLM as fact. Scrub it on
+ // load: no probe has run in this session, so 'unknown' is the honest value.
+ if (data.system && typeof data.system === 'object' && 'online' in data.system) {
+ delete data.system.online;
+ if (typeof data.system.network === 'undefined') data.system.network = 'unknown';
+ data.system.networkKnown = false;
+ }
this._model = data;
this._lastRefresh = data.synthesized;
}