diff options
| author | Agando <roduccomm@gmail.com> | 2026-06-04 06:01:04 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-06-04 06:01:04 +0200 |
| commit | a5023e61be61f2f2287342039addab6bb28ad2fd (patch) | |
| tree | 9b1b3c166869cd6fee91bffe1f4134513005b8b1 /modules/core/module-agent-registry.js | |
| parent | 301f75ab03302a05b3c35950a3b0ab49f9f69ed6 (diff) | |
auto: 2026-06-04_06:00 changes
Diffstat (limited to 'modules/core/module-agent-registry.js')
| -rw-r--r-- | modules/core/module-agent-registry.js | 128 |
1 files changed, 104 insertions, 24 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'); } |