aboutsummaryrefslogtreecommitdiff
path: root/modules/core/module-world-model.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-07-09 06:00:32 +0200
committerAgando <roduccomm@gmail.com>2026-07-09 06:00:32 +0200
commitcf7461ffeb4f53f3f50c9c692a2e4fdcecf17e82 (patch)
treee7780cd41271381cf108c9c81d71a3cb65cfd555 /modules/core/module-world-model.js
parentea4d09fb5ccba12ed774fcf7c913edfa1e6a851a (diff)
auto: 2026-07-09_06:00 changes
Diffstat (limited to 'modules/core/module-world-model.js')
-rw-r--r--modules/core/module-world-model.js96
1 files changed, 67 insertions, 29 deletions
diff --git a/modules/core/module-world-model.js b/modules/core/module-world-model.js
index 6f0c4e5..61eecfc 100644
--- a/modules/core/module-world-model.js
+++ b/modules/core/module-world-model.js
@@ -7,9 +7,14 @@ class WorldModelModule {
this.terminal = null;
this._model = null;
this._lastRefresh = null;
- this._REFRESH_INTERVAL = 300000; // 5 minutes
+ this._REFRESH_INTERVAL = 300000; // 5 minutes (fallback)
this._MAX_PROJECTS = 10;
this._refreshTimer = null;
+ // v4.5.89: Event-driven sync state
+ this._syncDebounce = null;
+ this._syncing = false;
+ this._lastEventSync = 0;
+ this._EVENT_DEBOUNCE_MS = 500;
}
getHelpInfo() {
@@ -51,6 +56,25 @@ class WorldModelModule {
this.terminal.print('🌐 World Model loaded — system self-awareness active', 'info');
}
+ // v4.5.89: Event-driven sync — debounced, recursion-guarded
+ _scheduleSync() {
+ if (this._syncing) return; // Prevent recursion during active synthesis
+ if (this._syncDebounce) clearTimeout(this._syncDebounce);
+ const now = Date.now();
+ // Skip if we synced < 2s ago (rate limit beyond debounce)
+ if (now - this._lastEventSync < 2000) return;
+ this._syncDebounce = setTimeout(() => {
+ this._syncDebounce = null;
+ this._syncing = true;
+ try {
+ this._synthesize();
+ this._lastEventSync = Date.now();
+ } finally {
+ this._syncing = false;
+ }
+ }, this._EVENT_DEBOUNCE_MS);
+ }
+
// ─── Synthesis Engine ───
_synthesize() {
@@ -119,9 +143,10 @@ class WorldModelModule {
try {
const mm = this.terminal?.moduleManager;
if (!mm) return 0;
- // Count registry entries (all known modules) vs loaded
- const registry = mm.moduleRegistry || [];
- return registry.length || Object.keys(mm.modules || {}).length;
+ // Use max of registry vs loaded so the ratio is never >1 (e.g. 43/7 → 43/43)
+ const loadedCount = Object.keys(mm.modules || {}).length;
+ const registryCount = (mm.moduleRegistry || []).length;
+ return Math.max(loadedCount, registryCount);
} catch (e) {
console.warn('[WorldModel] Total modules gather error:', e.message);
return Object.keys(this.terminal?.moduleManager?.modules || {}).length || 0;
@@ -129,10 +154,10 @@ class WorldModelModule {
}
_gatherSystemInfo(fs, moduleCount, version) {
- // Get real health data from ServerMonitorModule's _latencyLog and AI assistant's _errorLog
+ // Get real health data from ServerMonitorModule's _latencyLog and DebugSupervisorModule
const sm = this.terminal?.moduleManager?.modules?.['ServerMonitorModule'];
- const ai = this.terminal?.moduleManager?.modules?.['WebX11AIAssistantModule'];
- const errorCount = ai?._errorLog?.length || 0;
+ const ds = this.terminal?.moduleManager?.modules?.['DebugSupervisorModule'];
+ 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;
return {
@@ -228,11 +253,11 @@ class WorldModelModule {
_gatherTelemetry(fs) {
try {
- // Get real latency data from ServerMonitorModule and errors from AI assistant
+ // Get real latency data from ServerMonitorModule and errors from DebugSupervisorModule
const sm = this.terminal?.moduleManager?.modules?.['ServerMonitorModule'];
- const ai = this.terminal?.moduleManager?.modules?.['WebX11AIAssistantModule'];
+ const ds = this.terminal?.moduleManager?.modules?.['DebugSupervisorModule'];
const latencyDataPoints = sm?._latencyLog ? Object.keys(sm._latencyLog).length : 0;
- const recentErrors = ai?._errorLog?.length || 0;
+ const recentErrors = ds ? Object.keys(ds._errorDB?.errors || {}).length : 0;
return {
toolLatencyDataPoints: latencyDataPoints,
recentErrors: recentErrors,
@@ -352,41 +377,44 @@ class WorldModelModule {
if (!question) return this.terminal.print('Usage: world-model-ask <question>', 'error');
if (!this._model) return this.terminal.print('No model — run world-model-refresh first', 'warning');
- const q = question.toLowerCase();
+ const answer = this._answerQuery(question);
+ this.terminal.print(`❓ ${question}`, 'info');
+ this.terminal.print(`💡 ${answer}`, 'success');
+ }
+
+ // Shared query routing — used by both ask() (terminal) and world_model_query (AI tool)
+ _answerQuery(question) {
+ if (!this._model) return 'World model not synthesized yet — run world-model-refresh';
+ const q = (question || '').toLowerCase();
const m = this._model;
- let answer = '';
if (q.includes('module') || q.includes('loaded')) {
- answer = `Loaded ${m.modules.loaded} modules (${m.modules.modifiedCount} modified: ${m.modules.modified.join(', ') || 'none'})`;
+ return `Loaded ${m.modules.loaded} modules (${m.modules.modifiedCount} modified: ${m.modules.modified.join(', ') || 'none'})`;
} else if (q.includes('skill')) {
- answer = `Installed ${m.skills.count} skills: ${m.skills.skills.join(', ') || 'none'}`;
+ return `Installed ${m.skills.count} skills: ${m.skills.skills.join(', ') || 'none'}`;
} else if (q.includes('file') || q.includes('vfs')) {
- answer = `${m.files.total} files in ${m.files.directories} directories, total ${m.files.size}`;
+ return `${m.files.total} files in ${m.files.directories} directories, total ${m.files.size}`;
} else if (q.includes('agent') || q.includes('orchestrat')) {
- answer = `${m.agents.agents} agents (${m.agents.active} active)`;
+ return `${m.agents.agents} agents (${m.agents.active} active)`;
} else if (q.includes('health') || q.includes('status')) {
- answer = `Health: ${m.system.health} · Memory: ${m.system.memory} · ${m.telemetry.recentErrors > 0 ? `${m.telemetry.recentErrors} recent errors` : 'No errors'}`;
+ return `Health: ${m.system.health} · Memory: ${m.system.memory} · ${m.telemetry.recentErrors > 0 ? `${m.telemetry.recentErrors} recent errors` : 'No errors'}`;
} else if (q.includes('memory') || q.includes('graph')) {
- answer = `Memory Graph: ${m.memory.nodes} nodes, ${m.memory.edges} edges (${m.memory.active ? 'active' : 'inactive'})`;
+ return `Memory Graph: ${m.memory.nodes} nodes, ${m.memory.edges} edges (${m.memory.active ? 'active' : 'inactive'})`;
} else if (q.includes('change') || q.includes('yesterday') || q.includes('recent')) {
const recent = m.recent;
- answer = recent.length > 0 ? `Recent changes: ${recent.join('; ')}` : 'No recent changes detected';
+ return recent.length > 0 ? `Recent changes: ${recent.join('; ')}` : 'No recent changes detected';
} else if (q.includes('project') || q.includes('work')) {
const projects = m.projects.activeProjects;
- answer = projects.length > 0
+ return projects.length > 0
? `Active projects: ${projects.map(p => `${p.name} (${p.accesses}x)`).join(', ')}`
: 'No active projects detected';
} else if (q.includes('telemetry') || q.includes('latency')) {
- answer = `${m.telemetry.toolLatencyDataPoints} tool latency data points tracked`;
+ return `${m.telemetry.toolLatencyDataPoints} tool latency data points tracked, ${m.telemetry.recentErrors} recent errors`;
} else if (q.includes('summary') || q.includes('overview') || q.includes('all')) {
- answer = this.showSummary();
- return;
+ return `newOS4 ${m.version} · ${m.modules.loaded}/${m.modules.total} modules · ${m.skills.count} skills · ${m.files.total} files · ${m.agents.agents} agents · ${m.memory.nodes} graph nodes · ${m.telemetry.recentErrors > 0 ? `⚠️ ${m.telemetry.recentErrors} errors` : '✅ OK'}`;
} else {
- answer = `I'm not sure how to answer "${question}". Try: modules, skills, files, agents, health, memory, changes, projects, telemetry, or summary.`;
+ return `Unknown query. Try: modules, skills, files, agents, health, memory, changes, projects, telemetry, or summary.`;
}
-
- this.terminal.print(`❓ ${question}`, 'info');
- this.terminal.print(`💡 ${answer}`, 'success');
}
refresh() {
@@ -429,7 +457,7 @@ class WorldModelModule {
const agentStr = m.agents.error
? 'agents: N/A'
: `${m.agents.agents} agents`;
- return `[World Model Context]\\nSystem: newOS4 ${m.version} · ${m.modules.loaded} modules · ${m.skills.count} skills · ${m.files.total} files · ${agentStr}${m.telemetry.recentErrors > 0 ? ` · ⚠️ ${m.telemetry.recentErrors} errors` : ' · ✅ OK'}${m.modules.modifiedCount > 0 ? `\\nModified modules: ${m.modules.modified.slice(0, 3).join(', ')}${m.modules.modifiedCount > 3 ? ` (+${m.modules.modifiedCount - 3} more)` : ''}` : ''}`;
+ return `[World Model Context]\nSystem: newOS4 ${m.version} · ${m.modules.loaded} modules · ${m.skills.count} skills · ${m.files.total} files · ${agentStr}${m.telemetry.recentErrors > 0 ? ` · ⚠️ ${m.telemetry.recentErrors} errors` : ' · ✅ OK'}${m.modules.modifiedCount > 0 ? `\nModified modules: ${m.modules.modified.slice(0, 3).join(', ')}${m.modules.modifiedCount > 3 ? ` (+${m.modules.modifiedCount - 3} more)` : ''}` : ''}`;
}
// ─── Persistence ───
@@ -448,10 +476,20 @@ class WorldModelModule {
}
}
+ // v4.5.89: Atomic write — temp file + rename to prevent corruption
_saveToVFS(fs) {
try {
if (!fs.readDir('/system')) fs.mkdir('system');
- fs.fsWriteJSON('/system/world-model.json', this._model);
+ // v4.5.89b fix: Clean stale temp from previous crash (P1 from review)
+ try { fs.deleteFile('/system/world-model.tmp.json'); } catch (_) {}
+ fs.fsWriteJSON('/system/world-model.tmp.json', this._model);
+ const tmp = fs.fsReadJSON('/system/world-model.tmp.json');
+ if (tmp) {
+ fs.fsWriteJSON('/system/world-model.json', tmp);
+ fs.deleteFile('/system/world-model.tmp.json');
+ } else {
+ console.error('[WorldModel] Save failed: temp file corrupt after write');
+ }
} catch (e) {
console.error('[WorldModel] Save error:', e.message);
}