aboutsummaryrefslogtreecommitdiff
path: root/modules/core/module-agent-registry.js
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/module-agent-registry.js
parent301f75ab03302a05b3c35950a3b0ab49f9f69ed6 (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.js128
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');
}