aboutsummaryrefslogtreecommitdiff
path: root/modules/core
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-06-04 06:01:04 +0200
committerAgando <roduccomm@gmail.com>2026-06-04 06:01:04 +0200
commita5023e61be61f2f2287342039addab6bb28ad2fd (patch)
tree9b1b3c166869cd6fee91bffe1f4134513005b8b1 /modules/core
parent301f75ab03302a05b3c35950a3b0ab49f9f69ed6 (diff)
auto: 2026-06-04_06:00 changes
Diffstat (limited to 'modules/core')
-rw-r--r--modules/core/module-agent-registry.js128
-rw-r--r--modules/core/module-aiassist.js208
-rw-r--r--modules/core/module-loader.js6
-rw-r--r--modules/core/module-modmgr.js34
-rw-r--r--modules/core/module-neural-vfs.js271
-rw-r--r--modules/core/module-ollama.js177
-rw-r--r--modules/core/module-ollamamodels.js87
-rw-r--r--modules/core/module-orchestrator.js16
-rw-r--r--modules/core/module-servermon.js172
-rw-r--r--modules/core/terminal.js5
-rw-r--r--modules/core/virtual-fs.js52
11 files changed, 947 insertions, 209 deletions
diff --git a/modules/core/module-agent-registry.js b/modules/core/module-agent-registry.js
index 367f8b1..2439ab8 100644
--- a/modules/core/module-agent-registry.js
+++ b/modules/core/module-agent-registry.js
@@ -1,58 +1,137 @@
-// module-agent-registry.js — VFS-backed agent directory
-// Command: agent-list, agent-register, agent-unregister, agent-status
+// module-agent-registry.js — VFS-backed agent directory with A2A contracts
+// Commands: agent-list, agent-register, agent-unregister, agent-status, contract-register, contract-list
class AgentRegistryModule {
constructor() {
this.commands = {};
this.terminal = null;
+ this._contracts = {};
}
registerCommands() {
this.commands['agent-list'] = () => this.listAgents();
this.commands['agent-status'] = (args) => this.showAgentStatus(args[0]);
this.commands['agent-register'] = (args) => this.registerAgent(args);
this.commands['agent-unregister'] = (args) => this.unregisterAgent(args[0]);
+ this.commands['contract-register'] = (args) => this.registerContract(args);
+ this.commands['contract-list'] = () => this.listContracts();
}
init(terminal) {
this.terminal = terminal;
- if (!this.terminal.fs.readFile('/agents/')) {
- this.terminal.fs.mkdir('agents');
- }
+ if (!this.terminal.fs.readDir('/agents/')) this.terminal.fs.mkdir('agents');
+ if (!this.terminal.fs.readDir('/.contracts/')) this.terminal.fs.mkdir('.contracts');
+ this._loadContracts();
this.terminal.print('📋 Agent Registry loaded', 'info');
}
- listAgents() {
- const agents = this.terminal.fs.ls('/agents/');
- if (!agents || agents.length === 0) {
- return this.terminal.print('No agents registered', 'info');
+ _loadContracts() {
+ try {
+ const data = this.terminal.fs.readFile('/.contracts/index.json');
+ if (data) this._contracts = JSON.parse(data);
+ } catch (e) {}
+ // Try loading individual contract files
+ const dir = this.terminal.fs.readDir('/.contracts/');
+ if (dir) {
+ dir.forEach(e => {
+ if (e.type === 'file' && e.name.endsWith('.contract.json')) {
+ try {
+ const c = JSON.parse(this.terminal.fs.readFile('/.contracts/' + e.name));
+ this._contracts[c.role] = c;
+ } catch (e) { console.warn('Bad contract:', e.name, e.message); }
+ }
+ });
}
- this.terminal.print(`📋 Registered Agents (${agents.length}):`, 'header');
- agents.forEach(a => {
- if (a.type === 'dir' && a.name !== 'configured') {
- const data = this.terminal.fs.readFile(`/agents/${a.name}/status.json`);
- const s = data ? JSON.parse(data) : { status: 'unknown', capabilities: [] };
- this.terminal.print(` ${a.name} — ${s.status} (${(s.capabilities || []).length} capabilities)`, 'info');
- }
- });
+ }
+ _saveContracts() {
+ try {
+ this.terminal.fs.fsWriteJSON('/.contracts/index.json', this._contracts);
+ } catch (e) { console.error('Contract save error:', e); }
}
registerAgent(args) {
const id = args[1] || `agent-${Date.now()}`;
const capabilities = args.slice(2).length ? args.slice(2) : ['list_files', 'read_file'];
const agentDir = `agents/${id}`;
- if (!this.terminal.fs.readFile('/' + agentDir + '/')) this.terminal.fs.mkdir(agentDir);
+ if (!this.terminal.fs.readDir('/' + agentDir + '/')) this.terminal.fs.mkdir(agentDir);
this.terminal.fs.writeFile(`${agentDir}/status.json`, JSON.stringify({
id,
status: 'idle',
capabilities,
registered: new Date().toISOString(),
- lastHeartbeat: new Date().toISOString()
+ lastHeartbeat: new Date().toISOString(),
+ contract: null
}));
this.terminal.print(`✅ Agent '${id}' registered with ${capabilities.length} capabilities`, 'success');
}
- unregisterAgent(id) {
- const ok = this.terminal.fs.deleteFile(`agents/${id}`);
- if (ok) {
- this.terminal.print(`✅ Agent '${id}' unregistered`, 'success');
+ registerContract(args) {
+ const role = args[0];
+ if (!role) return this.terminal.print('Usage: contract-register <role> [input_fields...] [--output output_fields...]', 'warning');
+
+ const inputIdx = args.indexOf('--output');
+ const hasOutputFlag = inputIdx !== -1;
+
+ let inputFields = [], outputFields = [];
+ if (hasOutputFlag) {
+ inputFields = args.slice(1, inputIdx).filter(f => !f.startsWith('--'));
+ outputFields = args.slice(inputIdx + 1).filter(f => !f.startsWith('--'));
} else {
- this.terminal.print(`❌ Agent '${id}' not found`, 'error');
+ inputFields = args.slice(1).filter(f => !f.startsWith('--'));
+ outputFields = ['result']; // default output
+ }
+
+ const contract = {
+ role,
+ capabilities: inputFields,
+ input_schema: inputFields,
+ output_schema: outputFields,
+ created: new Date().toISOString(),
+ updated: new Date().toISOString()
+ };
+ this._contracts[role] = contract;
+ // Save as individual contract file
+ this.terminal.fs.fsWriteJSON(`/.contracts/${role}.contract.json`, contract);
+ this._saveContracts();
+ this.terminal.print(`✅ Contract registered for role '${role}'`, 'success');
+ this.terminal.print(` Input fields: ${inputFields.join(', ') || '(free-form)'}`, 'info');
+ this.terminal.print(` Output fields: ${outputFields.join(', ') || '(free-form)'}`, 'info');
+ }
+ listContracts() {
+ const roles = Object.keys(this._contracts);
+ if (roles.length === 0) return this.terminal.print('No contracts registered', 'info');
+ this.terminal.print(`📜 A2A Contracts (${roles.length}):`, 'header');
+ roles.forEach(r => {
+ const c = this._contracts[r];
+ this.terminal.print(` ${r} — input:[${c.input_schema.join(',')}] → output:[${c.output_schema.join(',')}]`, 'info');
+ });
+ }
+ validateTask(role, task) {
+ const contract = this._contracts[role];
+ if (!contract) return { valid: true, reason: 'no contract for role' }; // permissive fallback
+ if (contract.input_schema.length === 0) return { valid: true, reason: 'free-form input' };
+ // Simple validation: check required input fields are present in task
+ const missing = contract.input_schema.filter(f => !task.toLowerCase().includes(f.toLowerCase()));
+ if (missing.length > 0) {
+ return { valid: false, reason: `missing required fields: ${missing.join(', ')}` };
}
+ return { valid: true, reason: 'matched' };
+ }
+ getContract(role) {
+ return this._contracts[role] || null;
+ }
+ listAgents() {
+ const agents = this.terminal.fs.ls('/agents/');
+ if (!agents || agents.length === 0) {
+ return this.terminal.print('No agents registered', 'info');
+ }
+ this.terminal.print(`📋 Registered Agents (${agents.length}):`, 'header');
+ agents.forEach(a => {
+ if (a.type === 'dir' && a.name !== 'configured') {
+ const data = this.terminal.fs.readFile(`/agents/${a.name}/status.json`);
+ const s = data ? JSON.parse(data) : { status: 'unknown', capabilities: [] };
+ this.terminal.print(` ${a.name} — ${s.status} (${(s.capabilities || []).length} capabilities)${s.contract ? ` → contract:${s.contract}` : ''}`, 'info');
+ }
+ });
+ }
+ unregisterAgent(id) {
+ const ok = this.terminal.fs.deleteFile('agents/' + id);
+ if (ok) this.terminal.print(`✅ Agent '${id}' unregistered`, 'success');
+ else this.terminal.print(`❌ Agent '${id}' not found`, 'error');
}
showAgentStatus(id) {
if (!id) return this.terminal.print('Usage: agent-status <agent-id>', 'error');
@@ -62,6 +141,7 @@ class AgentRegistryModule {
this.terminal.print(`Agent: ${s.id}`, 'header');
this.terminal.print(` Status: ${s.status}`, 'info');
this.terminal.print(` Capabilities: ${(s.capabilities || []).join(', ')}`, 'info');
+ this.terminal.print(` Contract: ${s.contract || 'none'}`, 'info');
this.terminal.print(` Registered: ${s.registered}`, 'info');
this.terminal.print(` Last Heartbeat: ${s.lastHeartbeat}`, 'info');
}
diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js
index eb41af9..cb1482f 100644
--- a/modules/core/module-aiassist.js
+++ b/modules/core/module-aiassist.js
@@ -418,6 +418,56 @@ RULES:
required: ['role']
}
}
+ }, {
+ type: 'function',
+ function: {
+ name: 'nvfs_resolve',
+ description: 'Search VFS files by semantic concept using Neural VFS index. Call nvfs-index first to build the index.',
+ parameters: {
+ type: 'object',
+ properties: {
+ concept: { type: 'string', description: 'Concept to search (e.g. skill, memory, module, webdav, security)' }
+ },
+ required: ['concept']
+ }
+ }
+ }, {
+ type: 'function',
+ function: {
+ name: 'list_available_modules',
+ description: 'List all available modules with metadata (core/ext, commands, class name, layer). Filter by type with type=core or type=ext.',
+ parameters: {
+ type: 'object',
+ properties: {
+ type: { type: 'string', description: 'Optional: filter to "core" or "ext"' }
+ },
+ required: []
+ }
+ }
+ }, {
+ type: 'function',
+ function: {
+ name: 'smart_context_list',
+ description: 'List available AI context profiles (e.g. coding, debug, creative, technical). Returns profile names and system prompts.',
+ parameters: {
+ type: 'object',
+ properties: {},
+ required: []
+ }
+ }
+ }, {
+ type: 'function',
+ function: {
+ name: 'smart_context_set',
+ description: 'Set the active AI context profile. Changes the AI\'s behavior (temperature, style) for subsequent responses.',
+ parameters: {
+ type: 'object',
+ properties: {
+ name: { type: 'string', description: 'Profile name (e.g. "coding", "debug", "creative", "technical", "chat")' }
+ },
+ required: ['name']
+ }
+ }
}];
}
getHelpInfo() {
@@ -695,6 +745,9 @@ RULES:
duration: t1,
error: this._isToolError(toolResult) ? true : false
});
+ // Telemetry
+ const sm = this.terminal.moduleManager.modules['ServerMonitorModule'];
+ if (sm && sm.logToolCall) sm.logToolCall(fn.name, (t1 * 1000) || 1, this._isToolError(toolResult));
if (this._debugPanelEnabled) this._updateDebugPanel();
return { fn, toolResult, t1 };
}));
@@ -734,6 +787,9 @@ RULES:
duration: t1,
error: this._isToolError(toolResult) ? true : false
});
+ // Telemetry
+ const sm2 = this.terminal.moduleManager.modules['ServerMonitorModule'];
+ if (sm2 && sm2.logToolCall) sm2.logToolCall(fn.name, (t1 * 1000) || 1, this._isToolError(toolResult));
if (this._debugPanelEnabled) this._updateDebugPanel();
const dObs = this._summarizeObservation(toolResult, 150);
const dErr = this._isToolError(toolResult);
@@ -1338,6 +1394,76 @@ RULES:
.filter(s => !filterCat || (s.category || '').toLowerCase() === filterCat);
return JSON.stringify({ skills: list, count: list.length });
}
+ case 'nvfs_resolve': {
+ const concept = (a.concept || '').toLowerCase();
+ if (!concept) return JSON.stringify({ error: 'Concept is required' });
+ const nvfs = this.terminal.moduleManager.modules['NeuralVFSModule'];
+ if (!nvfs) return JSON.stringify({ error: 'Neural VFS module not loaded — run nvfs-index first' });
+ // Build index if empty
+ if (Object.keys(nvfs.index).length === 0) {
+ const saved = this.terminal.fs.fsReadJSON('/.semantic_index/map.json');
+ if (saved) { nvfs.index = saved; }
+ else { return JSON.stringify({ error: 'No semantic index found — run nvfs-index first' }); }
+ }
+ const exact = nvfs.index[concept];
+ const fuzzy = Object.keys(nvfs.index).filter(k =>
+ k.includes(concept) || concept.includes(k)
+ ).filter(k => k !== concept);
+ const results = [];
+ if (exact) results.push(...exact.map(e => ({ concept, path: e.path, summary: e.summary })));
+ for (const f of fuzzy.slice(0, 5)) {
+ results.push(...nvfs.index[f].slice(0, 3).map(e => ({ concept: f, path: e.path, summary: e.summary })));
+ }
+ return JSON.stringify({ concept, exact: !!exact, count: results.length, results: results.slice(0, 20) });
+ }
+ case 'list_available_modules': {
+ try {
+ const resp = await fetch('/spaces/newos4/modules-index.json?v=' + (localStorage.getItem('webx11_cache_ver') || ''));
+ if (!resp.ok) return JSON.stringify({ error: 'Module index not available' });
+ const idx = await resp.json();
+ const filterType = (a.type || '').toLowerCase();
+ let mods = Object.values(idx.modules);
+ if (filterType) mods = mods.filter(m => m.type === filterType);
+ return JSON.stringify({
+ total: mods.length,
+ available: idx.total,
+ type: filterType || 'all',
+ modules: mods.map(m => ({
+ name: m.name,
+ type: m.type,
+ class: m.class,
+ commands: m.commands.slice(0, 8),
+ description: (m.description || '').substring(0, 80),
+ auto_load: m.auto_load
+ }))
+ });
+ } catch (e) { return JSON.stringify({ error: 'Failed to load module index: ' + e.message }); }
+ }
+ case 'smart_context_list': {
+ const scm = this.terminal.moduleManager.modules['SmartContextModule'];
+ if (!scm) return JSON.stringify({ error: 'SmartContextModule not loaded', profiles: [] });
+ const profiles = scm.contextProfiles || {};
+ return JSON.stringify({
+ profiles: Object.entries(profiles).map(([key, p]) => ({
+ name: key,
+ displayName: p.name || key,
+ systemPrompt: (p.systemPrompt || '').substring(0, 80),
+ temperature: p.temperature || 0.7,
+ active: scm.activeProfile === key
+ }))
+ });
+ }
+ case 'smart_context_set': {
+ const scm = this.terminal.moduleManager.modules['SmartContextModule'];
+ if (!scm) return JSON.stringify({ error: 'SmartContextModule not loaded' });
+ const name = (a.name || '').toLowerCase();
+ if (!scm.contextProfiles[name]) {
+ return JSON.stringify({ error: `Profile '${name}' not found`, available: Object.keys(scm.contextProfiles) });
+ }
+ scm.activeProfile = name;
+ const profile = scm.contextProfiles[name];
+ return JSON.stringify({ success: true, name, displayName: profile.name, systemPrompt: profile.systemPrompt, temperature: profile.temperature });
+ }
case 'view_module':
case 'edit_module':
case 'test_module':
@@ -1364,16 +1490,41 @@ RULES:
const code = this.terminal.fs.readFile(`_mods/${modName}.js`);
if (code) return code;
const url = entry ? entry.url : null;
- if (!url) return JSON.stringify({ error: `No source URL for ${modName}` });
+ // Try registry URL first
+ if (url) {
+ try {
+ const resp = await fetch(url);
+ if (resp.ok) {
+ const src = await resp.text();
+ this.terminal.fs.writeFile(`_mods/${modName}.orig.js`, src);
+ return src.substring(0, 8000);
+ }
+ } catch (e) { /* fall through */ }
+ }
+ // Fallback: use modules-index.json to map class name to filename
try {
- const resp = await fetch(url);
- if (resp.ok) {
- const src = await resp.text();
- this.terminal.fs.writeFile(`_mods/${modName}.orig.js`, src);
- return src.substring(0, 8000);
+ const idxResp = await fetch('/spaces/newos4/modules-index.json?v=' + (localStorage.getItem('webx11_cache_ver') || ''));
+ if (idxResp.ok) {
+ const idx = await idxResp.json();
+ // modName is the class name (e.g. "WebX11AIAssistantModule")
+ // or already a filename (e.g. "module-aiassist")
+ // Try matching by class name first
+ let match = Object.values(idx.modules).find(m => m.class === modName);
+ // Then by name
+ if (!match) match = idx.modules[modName];
+ if (match) {
+ const filePath = match.file; // e.g. "modules/core/module-aiassist.js"
+ const fetchUrl = `/spaces/newos4/${filePath}`;
+ const resp = await fetch(fetchUrl);
+ if (resp.ok) {
+ const src = await resp.text();
+ this.terminal.fs.writeFile(`_mods/${modName}.orig.js`, src);
+ return src.substring(0, 8000);
+ }
+ }
}
- return JSON.stringify({ error: `HTTP ${resp.status}` });
- } catch (e) { return JSON.stringify({ error: e.message }); }
+ } catch (e) { /* fall through */ }
+ return JSON.stringify({ error: `Cannot find source for ${modName} — not in registry, not in modules-index.json` });
}
case 'edit_module': {
const modName = a.name || '';
@@ -1425,11 +1576,42 @@ RULES:
const mm = this.terminal.moduleManager;
const modsDir = this.terminal.fs.getCurrentDir();
const hasMods = modsDir.content['_mods'] ? !!Object.keys(modsDir.content['_mods'].content).length : false;
- const modules = mm.moduleRegistry.map(m => ({
- name: m.name, url: m.url, enabled: m.enabled,
- loaded: !!mm.modules[m.name],
- modified: !!this.terminal.fs.readFile(`_mods/${m.name}.js`)
- }));
+ // Load module index for file path lookup
+ let idxMap = {};
+ try {
+ const resp = await fetch('/spaces/newos4/modules-index.json?v=' + (localStorage.getItem('webx11_cache_ver') || ''));
+ if (resp.ok) {
+ const idx = await resp.json();
+ for (const [name, meta] of Object.entries(idx.modules)) {
+ idxMap[meta.class] = '/spaces/newos4/' + meta.file;
+ }
+ }
+ } catch (e) {}
+ const modules = [];
+ const seen = new Set();
+ for (const [className, instance] of Object.entries(mm.modules)) {
+ const name = instance.constructor ? instance.constructor.name : className;
+ if (seen.has(name)) continue;
+ seen.add(name);
+ modules.push({
+ name: name,
+ url: idxMap[name] || null,
+ enabled: true,
+ loaded: true,
+ modified: !!this.terminal.fs.readFile(`_mods/${name}.js`),
+ source: 'loaded'
+ });
+ }
+ for (const m of mm.moduleRegistry) {
+ if (seen.has(m.name)) continue;
+ seen.add(m.name);
+ modules.push({
+ name: m.name, url: m.url || idxMap[m.name] || null, enabled: m.enabled,
+ loaded: !!mm.modules[m.name],
+ modified: !!this.terminal.fs.readFile(`_mods/${m.name}.js`),
+ source: m.url ? 'ext' : 'core'
+ });
+ }
return JSON.stringify({ modules, total: modules.length, hasLocalEdits: hasMods });
}
default: return JSON.stringify({ error: `Unknown module tool: ${name}` });
diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js
index 2ffdb17..e7fc64a 100644
--- a/modules/core/module-loader.js
+++ b/modules/core/module-loader.js
@@ -18,7 +18,6 @@
'module-my.js',
'module-searxng.js',
'module-servermon.js',
- 'module-ollamamodels.js',
'module-config.js',
'module-simplegui.js',
'module-io.js',
@@ -33,7 +32,8 @@
'module-chatstudio.js',
'module-agent-registry.js',
'module-leases.js',
- 'module-orchestrator.js'
+ 'module-orchestrator.js',
+ 'module-neural-vfs.js'
];
function getSelectedModules() {
@@ -102,7 +102,7 @@
window.__moduleLoaderReload = reloadWithSelection;
-const LATEST_VER = '2026-06-02-webdav2';
+const LATEST_VER = '2026-06-03-neuralvfs';
const CACHE = '?v=' + LATEST_VER;
const stored = localStorage.getItem('webx11_cache_ver');
if (stored !== LATEST_VER) {
diff --git a/modules/core/module-modmgr.js b/modules/core/module-modmgr.js
index 1104883..32bb2cc 100644
--- a/modules/core/module-modmgr.js
+++ b/modules/core/module-modmgr.js
@@ -5,6 +5,7 @@
return {
'modules': 'modules — Open the module manager GUI',
'listmods': 'listmods — List all loaded modules with status',
+ 'modules-list': 'modules-list [core|ext] — List all available modules from the module index (46 total)',
'loadmod': 'loadmod <url> — Load a module from a URL',
'removemod': 'removemod <name> — Remove a loaded module by name',
'startup-modules': 'startup-modules — Open the startup modules selector',
@@ -36,5 +37,38 @@
this.terminal.print(`${status} ${m.name}${badge}`);
});
};
+ this.commands['modules-list'] = async (args) => {
+ try {
+ const filter = (args[0] || '').toLowerCase();
+ const resp = await fetch('/spaces/newos4/modules-index.json?v=' + (localStorage.getItem('webx11_cache_ver') || ''));
+ if (!resp.ok) return this.terminal.print('Failed to load module index', 'error');
+ const idx = await resp.json();
+ let mods = Object.values(idx.modules);
+ if (filter === 'core' || filter === 'ext') mods = mods.filter(m => m.type === filter);
+ if (filter === 'infra') mods = mods.filter(m => m.layer === 'infrastructure');
+ const loaded = this.terminal.moduleManager.modules || {};
+ const loadedCount = Object.keys(loaded).length;
+ const byLayer = {};
+ mods.forEach(m => {
+ const l = m.layer || m.type || 'other';
+ if (!byLayer[l]) byLayer[l] = [];
+ byLayer[l].push(m);
+ });
+ const layerSummary = Object.entries(byLayer).map(([l, ms]) => `${ms.length} ${l}`).join(', ');
+ this.terminal.print(`📦 Module Index (${mods.length} modules: ${layerSummary} — ${loadedCount} loaded)`, 'header');
+ for (const [layer, layerMods] of Object.entries(byLayer)) {
+ this.terminal.print(` ── ${layer.toUpperCase()} (${layerMods.length}) ──`, 'info');
+ for (const m of layerMods) {
+ const isLoaded = loaded[m.class] ? '✓' : ' ';
+ const layerTag = m.layer === 'infrastructure' ? '[infra]' : m.type === 'core' ? '[core]' : '[ext]';
+ const cmdHint = m.commands.length > 0 ? ` ${m.commands.slice(0, 4).join(', ')}${m.commands.length > 4 ? '...' : ''}` : '';
+ const desc = m.description ? ` — ${m.description.substring(0, 60)}` : '';
+ this.terminal.print(` ${isLoaded} ${layerTag} ${m.name}${cmdHint}${desc}`, '');
+ }
+ }
+ } catch (e) {
+ this.terminal.print(`modules-list error: ${e.message}`, 'error');
+ }
+ };
}
}
diff --git a/modules/core/module-neural-vfs.js b/modules/core/module-neural-vfs.js
new file mode 100644
index 0000000..e9ffafb
--- /dev/null
+++ b/modules/core/module-neural-vfs.js
@@ -0,0 +1,271 @@
+class NeuralVFSModule {
+ constructor() {
+ this.commands = {};
+ this.terminal = null;
+ this.index = {};
+ this.indexPath = '/.semantic_index/map.json';
+ }
+
+ getHelpInfo() {
+ return {
+ 'nvfs-index': 'nvfs-index — Scan VFS and build semantic concept index',
+ 'nvfs-status': 'nvfs-status — Show semantic index stats and last scan time',
+ 'nvfs-resolve': 'nvfs-resolve <concept> — Find files related to a concept',
+ 'nvfs-tree': 'nvfs-tree [concept] — Show concept-to-file mapping as a tree'
+ };
+ }
+
+ registerCommands() {
+ this.commands['nvfs-index'] = (args) => this.buildIndex();
+ this.commands['nvfs-status'] = (args) => this.showStatus();
+ this.commands['nvfs-resolve'] = (args) => this.resolve(args);
+ this.commands['nvfs-tree'] = (args) => this.tree(args);
+ }
+
+ // ── Core Index Builder ──
+
+ buildIndex() {
+ const fs = this.terminal.fs;
+ if (!fs) return this.terminal.print('VFS not available', 'error');
+
+ this.terminal.print('🔍 Scanning VFS for semantic concepts...', 'info');
+
+ const start = Date.now();
+ const newIndex = {};
+ let fileCount = 0;
+ let conceptCount = 0;
+
+ // Walk VFS recursively — skip system dirs
+ this._walk('/', fs, newIndex, (path, node) => {
+ if (node.type !== 'file') return;
+ if (this._isSystemFile(path)) return;
+
+ fileCount++;
+ const concepts = this._extractConcepts(path, node.content || '');
+ for (const concept of concepts) {
+ if (!newIndex[concept]) {
+ newIndex[concept] = [];
+ conceptCount++;
+ }
+ // Avoid dupes
+ if (!newIndex[concept].find(e => e.path === path)) {
+ newIndex[concept].push({ path, summary: this._summarize(node.content) });
+ }
+ }
+ });
+
+ // Also index skills
+ this._indexSkills(fs, newIndex);
+
+ this.index = newIndex;
+ this.lastScan = new Date().toISOString();
+ this.fileCount = fileCount;
+ this.conceptCount = conceptCount;
+
+ // Save to VFS
+ this._saveIndex(fs);
+ // Clear VFS concept cache so concept:// URIs reflect the new index
+ if (fs._clearConceptCache) fs._clearConceptCache();
+
+ const elapsed = ((Date.now() - start) / 1000).toFixed(1);
+ this.terminal.print(`✅ Indexed ${fileCount} files → ${conceptCount} concepts in ${elapsed}s`, 'success');
+ this.terminal.print(` Index saved to ${this.indexPath}`, 'info');
+ }
+
+ // ── Resolve ──
+
+ resolve(args) {
+ if (!args[0]) return this.terminal.print('Usage: nvfs-resolve <concept>', 'warning');
+
+ const concept = args.join(' ').toLowerCase();
+ const matches = this.index[concept] || [];
+
+ // Fuzzy fallback — find partial matches
+ const fuzzy = Object.keys(this.index).filter(k =>
+ k.includes(concept) || concept.includes(k)
+ ).filter(k => k !== concept);
+
+ if (matches.length === 0 && fuzzy.length === 0) {
+ return this.terminal.print(`No files found for concept "${concept}"`, 'warning');
+ }
+
+ if (matches.length > 0) {
+ this.terminal.print(`📂 "${concept}" → ${matches.length} file(s):`, 'info');
+ for (const m of matches.slice(0, 20)) {
+ this.terminal.print(` ${m.path}`, 'success');
+ if (m.summary) this.terminal.print(` ${m.summary}`, '')
+ }
+ if (matches.length > 20) {
+ this.terminal.print(` ... and ${matches.length - 20} more`, 'info');
+ }
+ }
+
+ if (fuzzy.length > 0) {
+ this.terminal.print(`\n🔀 Similar concepts: ${fuzzy.slice(0, 10).join(', ')}`, 'info');
+ }
+ }
+
+ // ── Status ──
+
+ showStatus() {
+ const count = Object.keys(this.index).length;
+ if (count === 0) {
+ // Try loading from VFS
+ const fs = this.terminal.fs;
+ const saved = fs.fsReadJSON(this.indexPath);
+ if (saved) {
+ this.index = saved;
+ this.lastScan = '(loaded from disk)';
+ this.terminal.print(`📖 Loaded index from ${this.indexPath}`, 'info');
+ } else {
+ return this.terminal.print('No semantic index found. Run nvfs-index first.', 'warning');
+ }
+ }
+
+ const totalFiles = new Set();
+ for (const entries of Object.values(this.index)) {
+ for (const e of entries) totalFiles.add(e.path);
+ }
+
+ this.terminal.print(`🧠 Neural VFS Index`, 'info');
+ this.terminal.print(` Concepts: ${Object.keys(this.index).length}`, '');
+ this.terminal.print(` Files indexed: ${totalFiles.size}`, '');
+ this.terminal.print(` Last scan: ${this.lastScan || 'never'}`, '');
+ }
+
+ // ── Tree View ──
+
+ tree(args) {
+ const concepts = Object.keys(this.index);
+ if (concepts.length === 0) {
+ return this.terminal.print('No index. Run nvfs-index first.', 'warning');
+ }
+
+ const filter = args[0] ? args[0].toLowerCase() : null;
+ const filtered = filter ? concepts.filter(c => c.includes(filter)) : concepts;
+
+ this.terminal.print(`🌳 Concept Tree${filter ? ` (filter: "${filter}")` : ''}:`, 'info');
+ for (const concept of filtered.slice(0, 15)) {
+ this.terminal.print(` 📁 ${concept}`, 'success');
+ for (const entry of this.index[concept].slice(0, 5)) {
+ this.terminal.print(` └─ ${entry.path}`, '');
+ }
+ if (this.index[concept].length > 5) {
+ this.terminal.print(` └─ ... (+${this.index[concept].length - 5} more)`, '');
+ }
+ }
+ if (filtered.length > 15) {
+ this.terminal.print(` ... and ${filtered.length - 15} more concepts`, 'info');
+ }
+ }
+
+ // ── Private Helpers ──
+
+ _walk(path, fs, index, cb) {
+ const entries = fs.readDir(path);
+ if (!entries) return;
+ for (const entry of entries) {
+ const fullPath = path === '/' ? '/' + entry.name : path + '/' + entry.name;
+ if (entry.type === 'file') {
+ cb(fullPath, { type: 'file', content: fs.readFile(fullPath) || '' });
+ } else if (entry.type === 'dir') {
+ this._walk(fullPath, fs, index, cb);
+ }
+ }
+ }
+
+ _isSystemFile(path) {
+ const sysPrefixes = ['/.trash', '/.semantic_index', '/_snapshots', '/_sessions'];
+ return sysPrefixes.some(p => path.startsWith(p) || path.includes('/node_modules'));
+ }
+
+ _extractConcepts(path, content) {
+ const concepts = new Set();
+ const name = path.split('/').pop() || '';
+ const nameLower = name.toLowerCase();
+
+ // Extract from filename (split on camelCase, kebab, dots, underscores)
+ const words = nameLower
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
+ .replace(/[-_.]/g, ' ')
+ .replace(/\.(md|js|html|json|css|txt|yaml|yml|toml|env|sh)$/i, '')
+ .split(/\s+/)
+ .filter(w => w.length > 2 && !/^\d+$/.test(w));
+
+ for (const word of words) {
+ concepts.add(word);
+ }
+
+ // Extract file extension as concept
+ const ext = path.split('.').pop();
+ if (ext && ['md', 'js', 'html', 'json', 'css'].includes(ext)) {
+ concepts.add(ext + '-file');
+ }
+
+ // Extract path-level concepts
+ const parts = path.split('/').filter(p => p.length > 0);
+ for (const part of parts) {
+ if (['modules', 'skills', 'core', 'ext', 'backups', 'chat', 'downloads'].includes(part)) {
+ concepts.add(part);
+ }
+ }
+
+ // Extract #tags from content (markdown frontmatter or inline)
+ if (content) {
+ const tagMatches = content.match(/tags:\s*\[([^\]]+)\]/i) || content.match(/#[a-zA-Z]\w+/g);
+ if (tagMatches) {
+ const tags = tagMatches[0].replace(/tags:\s*\[/i, '').replace(/\]/, '').split(',').map(t => t.trim().replace(/^#/, '').replace(/"/g, ''));
+ for (const tag of tags) {
+ if (tag.length > 2) concepts.add(tag.toLowerCase());
+ }
+ }
+
+ // Extract first H1/H2 as concept
+ const headerMatch = content.match(/^#\s+(.+)/m) || content.match(/^##\s+(.+)/m);
+ if (headerMatch) {
+ const header = headerMatch[1].toLowerCase().replace(/[^a-z0-9\s]/g, '').trim();
+ if (header.length > 3 && header.length < 40) {
+ concepts.add(header);
+ }
+ }
+ }
+
+ return [...concepts];
+ }
+
+ _summarize(content) {
+ if (!content || content.length < 10) return '';
+ const cleaned = content.replace(/---[\s\S]*?---/, '').trim();
+ const firstLine = cleaned.split('\n').find(l => l.trim().length > 20);
+ if (!firstLine) return '';
+ return firstLine.trim().slice(0, 100) + (firstLine.length > 100 ? '...' : '');
+ }
+
+ _indexSkills(fs, index) {
+ const skills = fs.readDir('/skills');
+ if (!skills) return;
+ for (const skill of skills) {
+ if (skill.type !== 'file' || !skill.name.endsWith('.md')) continue;
+ // Already indexed by _walk — but we add a "skill" tag
+ const base = skill.name.replace('.md', '');
+ if (!index['skill']) index['skill'] = [];
+ index['skill'].push({ path: '/skills/' + skill.name, summary: 'Skill: ' + base });
+ }
+ }
+
+ _saveIndex(fs) {
+ try {
+ if (!fs.readDir('/.semantic_index')) {
+ fs.mkdirRecursive('/.semantic_index');
+ }
+ fs.fsWriteJSON(this.indexPath, this.index);
+ } catch (e) {
+ console.error('NeuralVFS save error:', e);
+ }
+ }
+}
+
+// Required for module system
+if (typeof window !== 'undefined') {
+ window.NeuralVFSModule = NeuralVFSModule;
+} \ No newline at end of file
diff --git a/modules/core/module-ollama.js b/modules/core/module-ollama.js
index 3eabe19..ba8ad57 100644
--- a/modules/core/module-ollama.js
+++ b/modules/core/module-ollama.js
@@ -1,52 +1,131 @@
-// Extracted from newOS4 index.html — OllamaModule
- class OllamaModule {
- constructor() { this.commands = {}; this.terminal = null; }
- getHelpInfo() {
- return {
- 'ollama': 'ollama — Open the Ollama model manager UI',
- 'ollamaconnect': 'ollamaconnect — Connect to the Ollama server',
- 'ollamadisconnect': 'ollamadisconnect — Disconnect from the Ollama server',
- 'ollamamodels': 'ollamamodels — List all available models from Ollama',
- 'ollamasetmodel': 'ollamasetmodel <name> — Set the active Ollama model',
- 'ollamasetproxy': 'ollamasetproxy [endpoint] — View or change the Ollama endpoint URL',
- };
+// Extracted from newOS4 index.html — OllamaModule (merged with OllamaModelsModule)
+// Commands: ollama, ollamaconnect, ollamadisconnect, ollamamodels, ollamasetmodel, ollamasetproxy, models, model-info, model-pull, model-delete, model-run, ai, ask
+class OllamaModule {
+ constructor() { this.commands = {}; this.terminal = null; }
+ getHelpInfo() {
+ return {
+ 'ollama': 'ollama — Open the Ollama model manager UI',
+ 'ollamaconnect': 'ollamaconnect — Connect to the Ollama server',
+ 'ollamadisconnect': 'ollamadisconnect — Disconnect from the Ollama server',
+ 'ollamamodels': 'ollamamodels — List all available models from Ollama',
+ 'models': 'models — List all available Ollama models (table format)',
+ 'model-info': 'model-info <model-name> — Show details about a specific model',
+ 'model-pull': 'model-pull <model-name> — Pull a model from Ollama registry',
+ 'model-delete': 'model-delete <model-name> — Delete a model from local storage',
+ 'model-run': 'model-run <model-name> [prompt] — Run inference with a model',
+ 'ollamasetmodel': 'ollamasetmodel <name> — Set the active Ollama model',
+ 'ollamasetproxy': 'ollamasetproxy [endpoint] — View or change the Ollama endpoint URL',
+ };
+ }
+ registerCommands() {
+ this.commands['ollama'] = () => { this.terminal.openOllamaManager(); };
+ this.commands['ollamaconnect'] = async () => {
+ await this.terminal.ollamaManager.connect();
+ this.terminal.updateOllamaStatus();
+ };
+ this.commands['ollamadisconnect'] = () => {
+ this.terminal.ollamaManager.connected = false;
+ this.terminal.ollamaManager.models = [];
+ this.terminal.print('Ollama disconnected', 'info');
+ this.terminal.updateOllamaStatus();
+ };
+ this.commands['ollamamodels'] = () => {
+ const models = this.terminal.ollamaManager.getModels();
+ if(models.length === 0) return this.terminal.print("No models found. Connect first.");
+ models.forEach(m => this.terminal.print(`${m.name} (${(m.size/1e9).toFixed(1)}GB)`));
+ };
+ this.commands['ollamasetmodel'] = (args) => {
+ if(!args[0]) return this.terminal.print("Usage: ollamasetmodel <name>", 'warning');
+ this.terminal.ollamaManager.setModel(args[0]);
+ };
+ this.commands['ollamasetproxy'] = (args) => {
+ if(!args[0]) {
+ const ep = this.terminal.ollamaManager.endpoint;
+ return this.terminal.print(`Current Ollama endpoint: ${ep}`, 'info');
}
- registerCommands() {
- this.commands['ollama'] = () => { this.terminal.openOllamaManager(); };
- this.commands['ollamaconnect'] = async () => {
- await this.terminal.ollamaManager.connect();
- this.terminal.updateOllamaStatus();
- };
- this.commands['ollamadisconnect'] = () => {
- this.terminal.ollamaManager.connected = false;
- this.terminal.ollamaManager.models = [];
- this.terminal.print('Ollama disconnected', 'info');
- this.terminal.updateOllamaStatus();
- };
- this.commands['ollamamodels'] = () => {
- const models = this.terminal.ollamaManager.getModels();
- if(models.length === 0) return this.terminal.print("No models found. Connect first.");
- models.forEach(m => this.terminal.print(`${m.name} (${(m.size/1e9).toFixed(1)}GB)`));
- };
- this.commands['ollamasetmodel'] = (args) => {
- if(!args[0]) return this.terminal.print("Usage: ollamasetmodel <name>", 'warning');
- this.terminal.ollamaManager.setModel(args[0]);
- };
- this.commands['ollamasetproxy'] = (args) => {
- if(!args[0]) {
- const ep = this.terminal.ollamaManager.endpoint;
- return this.terminal.print(`Current Ollama endpoint: ${ep}`, 'info');
- }
- this.terminal.ollamaManager.setEndpoint(args[0]);
- this.terminal.print(`Ollama endpoint set to: ${args[0]}`, 'success');
- };
- this.commands['ai'] = async (args) => {
- if(!args[0]) return this.terminal.print("Usage: ai <prompt>", 'warning');
- await this.terminal.ollamaManager.chat(args.join(' '));
- };
- this.commands['ask'] = async (args) => {
- if(!args[0]) return this.terminal.print("Usage: ask <prompt>", 'warning');
- await this.terminal.ollamaManager.chat(args.join(' '));
- };
+ this.terminal.ollamaManager.setEndpoint(args[0]);
+ this.terminal.print(`Ollama endpoint set to: ${args[0]}`, 'success');
+ };
+ this.commands['ai'] = async (args) => {
+ if(!args[0]) return this.terminal.print("Usage: ai <prompt>", 'warning');
+ await this.terminal.ollamaManager.chat(args.join(' '));
+ };
+ this.commands['ask'] = async (args) => {
+ if(!args[0]) return this.terminal.print("Usage: ask <prompt>", 'warning');
+ await this.terminal.ollamaManager.chat(args.join(' '));
+ };
+ // Merged from OllamaModelsModule
+ this.commands['models'] = () => this.listModels();
+ this.commands['model-info'] = (args) => this.modelInfo(args);
+ this.commands['model-pull'] = (args) => this.pullModel(args);
+ this.commands['model-delete'] = (args) => this.deleteModel(args);
+ this.commands['model-run'] = (args) => this.runModel(args);
+ }
+ async listModels() {
+ const endpoint = localStorage.getItem('webx11_ollama_endpoint') || 'http://localhost:11434';
+ try {
+ const response = await fetch(`${endpoint}/api/tags`);
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+ const data = await response.json();
+ const models = data.models || [];
+ if (models.length === 0) {
+ this.terminal.print("No models found. Run 'ollama pull <model>' first.", "warning"); return;
}
+ this.terminal.print("=== Available Ollama Models ===", "info");
+ this.terminal.printHTML(`<table style="width: 100%; border-collapse: collapse; margin: 10px 0;">
+ <tr style="border-bottom: 1px solid #005500;">
+ <th style="text-align: left; padding: 5px;">Name</th>
+ <th style="text-align: left; padding: 5px;">Size</th>
+ <th style="text-align: left; padding: 5px;">Family</th>
+ </tr>`);
+ models.forEach(m => {
+ const sizeGB = (m.size / 1e9).toFixed(2);
+ this.terminal.printHTML(`<tr style="border-bottom: 1px solid #111;">
+ <td style="padding: 5px; color: #00ff00;">${m.name}</td>
+ <td style="padding: 5px; color: #888888;">${sizeGB} GB</td>
+ <td style="padding: 5px; color: #00aaff;">${m.details?.family || 'N/A'}</td>
+ </tr>`);
+ });
+ this.terminal.printHTML(`</table>`);
+ } catch (e) {
+ this.terminal.print(`Failed to fetch models: ${e.message}`, "error");
}
+ }
+ async modelInfo(args) {
+ if (!args[0]) {
+ this.terminal.print("Usage: model-info <model-name>", "warning"); return;
+ }
+ const endpoint = localStorage.getItem('webx11_ollama_endpoint') || 'http://localhost:11434';
+ try {
+ const response = await fetch(`${endpoint}/api/show`, {
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ name: args[0] })
+ });
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+ const data = await response.json();
+ this.terminal.print(`=== ${args[0]} ===`, "info");
+ this.terminal.print(`License: ${data.license || 'N/A'}`);
+ this.terminal.print(`Parameters: ${data.details?.parameter_size || 'N/A'}`);
+ this.terminal.print(`Family: ${data.details?.family || 'N/A'}`);
+ } catch (e) {
+ this.terminal.print(`Failed to get model info: ${e.message}`, "error");
+ }
+ }
+ pullModel(args) {
+ this.terminal.print("⚠️ Model pulling must be done via Ollama CLI", "warning");
+ this.terminal.print(`Run: ollama pull ${args[0] || '<model-name>'}`, "info");
+ }
+ deleteModel(args) {
+ this.terminal.print("⚠️ Model deletion must be done via Ollama CLI", "warning");
+ this.terminal.print(`Run: ollama rm ${args[0] || '<model-name>'}`, "info");
+ }
+ async runModel(args) {
+ if (!args[0]) {
+ this.terminal.print("Usage: model-run <model-name> [prompt]", "warning"); return;
+ }
+ const model = args[0];
+ const prompt = args.slice(1).join(' ') || 'Hello!';
+ this.terminal.print(`Running ${model}...`, "info");
+ await this.terminal.ollamaManager.chat(prompt, model);
+ }
+} \ No newline at end of file
diff --git a/modules/core/module-ollamamodels.js b/modules/core/module-ollamamodels.js
deleted file mode 100644
index 9a7763a..0000000
--- a/modules/core/module-ollamamodels.js
+++ /dev/null
@@ -1,87 +0,0 @@
-// Extracted from newOS4 index.html — OllamaModelsModule
- class OllamaModelsModule {
- constructor() { this.commands = {}; this.terminal = null; }
- getHelpInfo() {
- return {
- 'models': 'models — List all available Ollama models',
- 'model-info': 'model-info <model-name> — Show details about a specific model',
- 'model-pull': 'model-pull <model-name> — Pull a model from Ollama registry',
- 'model-delete': 'model-delete <model-name> — Delete a model from local storage',
- 'model-run': 'model-run <model-name> [prompt] — Run inference with a model'
- };
- }
- registerCommands() {
- this.commands['models'] = () => this.listModels();
- this.commands['model-info'] = (args) => this.modelInfo(args);
- this.commands['model-pull'] = (args) => this.pullModel(args);
- this.commands['model-delete'] = (args) => this.deleteModel(args);
- this.commands['model-run'] = (args) => this.runModel(args);
- }
- async listModels() {
- const endpoint = localStorage.getItem('webx11_ollama_endpoint') || 'http://localhost:11434';
- try {
- const response = await fetch(`${endpoint}/api/tags`);
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
- const data = await response.json();
- const models = data.models || [];
- if (models.length === 0) {
- this.terminal.print("No models found. Run 'ollama pull <model>' first.", "warning"); return;
- }
- this.terminal.print("=== Available Ollama Models ===", "info");
- this.terminal.printHTML(`<table style="width: 100%; border-collapse: collapse; margin: 10px 0;">
- <tr style="border-bottom: 1px solid #005500;">
- <th style="text-align: left; padding: 5px;">Name</th>
- <th style="text-align: left; padding: 5px;">Size</th>
- <th style="text-align: left; padding: 5px;">Family</th>
- </tr>`);
- models.forEach(m => {
- const sizeGB = (m.size / 1e9).toFixed(2);
- this.terminal.printHTML(`<tr style="border-bottom: 1px solid #111;">
- <td style="padding: 5px; color: #00ff00;">${m.name}</td>
- <td style="padding: 5px; color: #888888;">${sizeGB} GB</td>
- <td style="padding: 5px; color: #00aaff;">${m.details?.family || 'N/A'}</td>
- </tr>`);
- });
- this.terminal.printHTML(`</table>`);
- } catch (e) {
- this.terminal.print(`Failed to fetch models: ${e.message}`, "error");
- }
- }
- async modelInfo(args) {
- if (!args[0]) {
- this.terminal.print("Usage: model-info <model-name>", "warning"); return;
- }
- const endpoint = localStorage.getItem('webx11_ollama_endpoint') || 'http://localhost:11434';
- try {
- const response = await fetch(`${endpoint}/api/show`, {
- method: 'POST', headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ name: args[0] })
- });
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
- const data = await response.json();
- this.terminal.print(`=== ${args[0]} ===`, "info");
- this.terminal.print(`License: ${data.license || 'N/A'}`);
- this.terminal.print(`Parameters: ${data.details?.parameter_size || 'N/A'}`);
- this.terminal.print(`Family: ${data.details?.family || 'N/A'}`);
- } catch (e) {
- this.terminal.print(`Failed to get model info: ${e.message}`, "error");
- }
- }
- pullModel(args) {
- this.terminal.print("⚠️ Model pulling must be done via Ollama CLI", "warning");
- this.terminal.print(`Run: ollama pull ${args[0] || '<model-name>'}`, "info");
- }
- deleteModel(args) {
- this.terminal.print("⚠️ Model deletion must be done via Ollama CLI", "warning");
- this.terminal.print(`Run: ollama rm ${args[0] || '<model-name>'}`, "info");
- }
- async runModel(args) {
- if (!args[0]) {
- this.terminal.print("Usage: model-run <model-name> [prompt]", "warning"); return;
- }
- const model = args[0];
- const prompt = args.slice(1).join(' ') || 'Hello!';
- this.terminal.print(`Running ${model}...`, "info");
- await this.terminal.ollamaManager.chat(prompt, model);
- }
- }
diff --git a/modules/core/module-orchestrator.js b/modules/core/module-orchestrator.js
index 4fab599..de88424 100644
--- a/modules/core/module-orchestrator.js
+++ b/modules/core/module-orchestrator.js
@@ -75,6 +75,22 @@ class OrchestratorModule {
agent = Object.values(this._agents).find(a => a.status === 'idle');
}
if (!agent) return this.terminal.print('❌ No available agents', 'error');
+
+ // A2A Contract handshake — validate task against agent's contract
+ const registry = this.terminal.moduleManager.modules['AgentRegistryModule'];
+ let contractValid = true;
+ if (registry) {
+ const result = registry.validateTask(agent.role, task);
+ if (!result.valid) {
+ this.terminal.print(`⚠️ A2A validation: ${result.reason}`, 'warning');
+ this.terminal.print(` Contract for '${agent.role}' requires: ${(registry.getContract(agent.role) || {}).input_schema || 'free-form'}`, 'warning');
+ // Offer to proceed anyway (permissive) — user can create a proper contract
+ contractValid = false;
+ } else if (result.reason === 'matched') {
+ this.terminal.print(`✅ A2A contract matched for role '${agent.role}'`, 'success');
+ }
+ }
+
// Create a lease
const leaseModule = this.terminal.moduleManager.modules['LeaseModule'];
const lease = leaseModule ? leaseModule.createLease(['lease-create', agent.id, '60', task]) : null;
diff --git a/modules/core/module-servermon.js b/modules/core/module-servermon.js
index 23407db..293e9af 100644
--- a/modules/core/module-servermon.js
+++ b/modules/core/module-servermon.js
@@ -1,35 +1,143 @@
-// Extracted from newOS4 index.html — ServerMonitorModule
- class ServerMonitorModule {
- constructor() {
- this.commands = {}; this.terminal = null;
- this.servers = this.loadServers(); this.monitorInterval = null;
- }
- getHelpInfo() {
- return {
- 'server': 'Add a server to monitor: server <name> <url>',
- 'servers': 'List all monitored servers',
- 'server-check': 'Check a server status: server-check <name-or-index>',
- 'server-remove': 'Remove a server: server-remove <name-or-index>',
- 'monitor': 'Toggle auto-monitoring (checks every 60s)'
- };
- }
- loadServers() {
- try {
- const saved = localStorage.getItem('webx11_servers');
- return saved ? JSON.parse(saved) : [];
- } catch (e) { return []; }
- }
- saveServers() {
- localStorage.setItem('webx11_servers', JSON.stringify(this.servers));
- }
- registerCommands() {
- this.commands['server'] = (args) => this.addServer(args);
- this.commands['servers'] = () => this.listServers();
- this.commands['server-check'] = (args) => this.checkServer(args);
- this.commands['server-remove'] = (args) => this.removeServer(args);
- this.commands['monitor'] = (args) => this.toggleMonitor(args);
- }
- addServer(args) {
+// module-servermon.js — Server monitoring + self-optimization telemetry
+// Commands: server, servers, server-check, server-remove, monitor, system-health
+class ServerMonitorModule {
+ constructor() {
+ this.commands = {}; this.terminal = null;
+ this.servers = this.loadServers(); this.monitorInterval = null;
+ this._latencyLog = [];
+ this._maxLatencyEntries = 200;
+ }
+ getHelpInfo() {
+ return {
+ 'server': 'server <name> <url> — Add a server to monitor',
+ 'servers': 'servers — List all monitored servers',
+ 'server-check': 'server-check <name-or-index> — Check a server status',
+ 'server-remove': 'server-remove <name-or-index> — Remove a server',
+ 'monitor': 'monitor — Toggle auto-monitoring (checks every 60s)',
+ 'system-health': 'system-health — Show tool latency, module load times, VFS size, and error rates'
+ };
+ }
+ registerCommands() {
+ this.commands['server'] = (args) => this.addServer(args);
+ this.commands['servers'] = () => this.listServers();
+ this.commands['server-check'] = (args) => this.checkServer(args);
+ this.commands['server-remove'] = (args) => this.removeServer(args);
+ this.commands['monitor'] = (args) => this.toggleMonitor(args);
+ this.commands['system-health'] = () => this.showSystemHealth();
+ }
+ logToolCall(name, durationMs, error) {
+ this._latencyLog.push({
+ name,
+ duration: durationMs,
+ error: !!error,
+ time: Date.now()
+ });
+ if (this._latencyLog.length > this._maxLatencyEntries) {
+ this._latencyLog = this._latencyLog.slice(-this._maxLatencyEntries);
+ }
+ // Persist every 20 calls
+ if (this._latencyLog.length % 20 === 0) {
+ this._persistLatency();
+ }
+ }
+ _persistLatency() {
+ try {
+ this.terminal.fs.fsWriteJSON('/system/metrics/tool-latency.json', this._latencyLog.slice(-100));
+ } catch (e) {}
+ }
+ _loadLatency() {
+ try {
+ const data = this.terminal.fs.fsReadJSON('/system/metrics/tool-latency.json');
+ if (data) this._latencyLog = data;
+ } catch (e) {}
+ }
+ showSystemHealth() {
+ this._loadLatency();
+ const fs = this.terminal.fs;
+
+ // Tool latency stats
+ const toolStats = {};
+ for (const entry of this._latencyLog) {
+ if (!toolStats[entry.name]) toolStats[entry.name] = { calls: 0, totalMs: 0, errors: 0, maxMs: 0 };
+ toolStats[entry.name].calls++;
+ toolStats[entry.name].totalMs += entry.duration;
+ toolStats[entry.name].errors += entry.error ? 1 : 0;
+ toolStats[entry.name].maxMs = Math.max(toolStats[entry.name].maxMs, entry.duration);
+ }
+
+ this.terminal.print('📊 System Health', 'header');
+
+ // Tool latency table (sorted by avg desc)
+ this.terminal.print('── Tool Latency ──', 'info');
+ const sorted = Object.entries(toolStats)
+ .map(([name, s]) => ({ name, avg: (s.totalMs / s.calls).toFixed(0), ...s }))
+ .sort((a, b) => b.avg - a.avg);
+ for (const t of sorted.slice(0, 10)) {
+ const avgMs = t.avg.padStart(5);
+ const maxMs = String(t.maxMs).padStart(5);
+ const err = t.errors > 0 ? ` ⚠️${t.errors}` : '';
+ this.terminal.print(` ${avgMs}ms avg / ${maxMs}ms max — ${t.name} (${t.calls} calls)${err}`, t.errors > 0 ? 'warning' : '');
+ }
+ if (sorted.length > 10) this.terminal.print(` ... and ${sorted.length - 10} more tools`, 'info');
+
+ // VFS stats
+ this.terminal.print('── VFS Stats ──', 'info');
+ try {
+ const totalSize = this._vfsSize(fs.root);
+ const fileCount = this._vfsCount(fs.root, 'file');
+ const dirCount = this._vfsCount(fs.root, 'dir');
+ this.terminal.print(` ${fileCount} files, ${dirCount} dirs, ${(totalSize / 1024).toFixed(0)} KB total`, '');
+ } catch (e) { this.terminal.print(' VFS stats unavailable', 'warning'); }
+
+ // Module stats
+ this.terminal.print('── Module Stats ──', 'info');
+ const mm = this.terminal.moduleManager;
+ const loaded = Object.keys(mm.modules).length;
+ const reg = (mm.moduleRegistry || []).length;
+ this.terminal.print(` ${loaded} modules loaded, ${reg} in registry`, '');
+
+ // Memory
+ this.terminal.print('── Memory ──', 'info');
+ if (performance.memory) {
+ const heap = (performance.memory.usedJSHeapSize / 1048576).toFixed(1);
+ const total = (performance.memory.jsHeapSizeLimit / 1048576).toFixed(0);
+ this.terminal.print(` ${heap} MB used / ${total} MB limit`, heap > 100 ? 'warning' : '');
+ } else {
+ this.terminal.print(' Memory info not available (non-Chrome browser)', '');
+ }
+
+ // Total tool call count
+ const totalCalls = this._latencyLog.length;
+ const totalErrors = this._latencyLog.filter(e => e.error).length;
+ this.terminal.print(` ${totalCalls} tool calls logged, ${totalErrors} errors (${totalCalls > 0 ? (totalErrors / totalCalls * 100).toFixed(1) : 0}% error rate)`, totalErrors > 0 ? 'warning' : '');
+ }
+ _vfsSize(node) {
+ if (!node) return 0;
+ if (node.type === 'file') return (node.content || '').length;
+ let s = 0;
+ for (const k in (node.content || {})) s += this._vfsSize(node.content[k]);
+ return s;
+ }
+ _vfsCount(node, type) {
+ if (!node) return 0;
+ let c = 0;
+ for (const k in (node.content || {})) {
+ const child = node.content[k];
+ if (child.type === type) c++;
+ c += this._vfsCount(child, type);
+ }
+ return c;
+ }
+ loadServers() {
+ try {
+ const saved = localStorage.getItem('webx11_servers');
+ return saved ? JSON.parse(saved) : [];
+ } catch (e) { return []; }
+ }
+ saveServers() {
+ localStorage.setItem('webx11_servers', JSON.stringify(this.servers));
+ }
+ addServer(args) {
if (args.length < 2) {
this.terminal.print("Usage: server <name> <url>", "warning"); return;
}
diff --git a/modules/core/terminal.js b/modules/core/terminal.js
index 4111f5f..e3f222c 100644
--- a/modules/core/terminal.js
+++ b/modules/core/terminal.js
@@ -14,7 +14,7 @@
this._registerBuiltIn();
}
_registerBuiltIn() {
- const internals = ['clear','help','ls','cat','df','free','env','history','cd','pwd','touch','mkdir','rm','cp','mv','edit','cat','run','font-size','load-local','loadlocal','mem','security','security-level','startup-modules','smart-prompt','cmd-allow','cmd-deny'];
+ const internals = ['clear','help','ls','cat','df','free','env','history','cd','pwd','touch','mkdir','rm','cp','mv','edit','cat','run','font-size','load-local','loadlocal','mem','security','security-level','startup-modules','smart-prompt','cmd-allow','cmd-deny','system-health','server','servers','server-check','monitor'];
internals.forEach(c => this.registerCommand(c, { category: 'builtin' }));
}
setLevel(n) {
@@ -1027,6 +1027,9 @@
this._safeRegister('AgentRegistryModule', 'agent registry');
this._safeRegister('LeaseModule', 'lease system');
this._safeRegister('OrchestratorModule', 'orchestrator');
+ this._safeRegister('NeuralVFSModule', 'neural VFS');
+ this._safeRegister('ServerMonitorModule', 'server monitor');
+ this._safeRegister('SimpleGUIModule', 'simple GUI builder');
}
_safeRegister(className, label) {
let cls;
diff --git a/modules/core/virtual-fs.js b/modules/core/virtual-fs.js
index d2d3103..3ab25b1 100644
--- a/modules/core/virtual-fs.js
+++ b/modules/core/virtual-fs.js
@@ -3,6 +3,7 @@ class VirtualFileSystem {
this.root = { type: 'dir', content: {} };
this.currentPath = [];
this._ready = this.loadFromStorage();
+ this._conceptCache = null;
}
_u8ToBase64(u8) {
let s = '';
@@ -82,6 +83,27 @@ class VirtualFileSystem {
} catch (e) { console.error('FS save error:', e); }
}, 2000);
}
+ getCurrentPath() {
+ return '/' + this.currentPath.join('/');
+ }
+ setCurrentPath(path) {
+ const segs = this._toAbsoluteSegments(path);
+ if (segs === null) return false;
+ this.currentPath = segs;
+ return true;
+ }
+ _loadConceptIndex() {
+ if (this._conceptCache) return this._conceptCache;
+ try {
+ const raw = this.readFile('/.semantic_index/map.json');
+ if (raw) {
+ this._conceptCache = JSON.parse(raw);
+ return this._conceptCache;
+ }
+ } catch (e) {}
+ return null;
+ }
+ _clearConceptCache() { this._conceptCache = null; }
getCurrentDir() {
let dir = this.root;
for (let part of this.currentPath) {
@@ -130,6 +152,18 @@ class VirtualFileSystem {
return dir;
}
ls(path) {
+ // concept:// listing
+ if (path && path.startsWith('concept://')) {
+ const concept = path.replace('concept://', '').replace(/\/.*$/, '').toLowerCase();
+ const index = this._loadConceptIndex();
+ if (!index) return [];
+ let matches = index[concept] || [];
+ if (matches.length === 0) {
+ const fuzzy = Object.keys(index).filter(k => k.includes(concept) || concept.includes(k));
+ if (fuzzy.length > 0) matches = index[fuzzy[0]];
+ }
+ return (matches || []).map(m => ({ name: m.path.split('/').pop(), type: 'file', path: m.path, size: 0, concept: true }));
+ }
let dir;
if (path) {
const resolved = this._resolvePath(path);
@@ -161,6 +195,24 @@ class VirtualFileSystem {
return true;
}
readFile(path) {
+ // concept:// URI resolution
+ if (path && path.startsWith('concept://')) {
+ const concept = path.replace('concept://', '').replace(/\/.*$/, '').toLowerCase();
+ try {
+ const index = this._loadConceptIndex();
+ if (!index) return 'No semantic index found. Run nvfs-index first.';
+ let matches = index[concept];
+ if (!matches) {
+ const fuzzy = Object.keys(index).filter(k => k.includes(concept) || concept.includes(k));
+ if (fuzzy.length > 0) matches = index[fuzzy[0]];
+ }
+ if (!matches || matches.length === 0) return `No files found for concept "${concept}"`;
+ const first = matches[0];
+ const content = this.readFile(first.path);
+ if (content) return `--- ${first.path} (concept://${concept}) ---\n${content}`;
+ return `Concept "${concept}" → ${matches.length} file(s):\n${matches.map(m => ` ${m.path} — ${m.summary || ''}`).join('\n')}`;
+ } catch (e) { return `concept:// error: ${e.message}`; }
+ }
const resolved = this._resolvePath(path);
if (!resolved || !resolved.name) return null;
const node = resolved.dir.content[resolved.name];