aboutsummaryrefslogtreecommitdiff
path: root/modules/core/module-orchestrator.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-06-02 18:00:37 +0200
committerAgando <roduccomm@gmail.com>2026-06-02 18:00:37 +0200
commitaafb0fbde5289a45f9f818a00141f80b149be283 (patch)
treed4d8f588187719c99240fbaef1940c068402b892 /modules/core/module-orchestrator.js
parent6d41e590a38c73ef60f1c04beaf8f5438f350f46 (diff)
auto: 2026-06-02_18:00 changes
Diffstat (limited to 'modules/core/module-orchestrator.js')
-rw-r--r--modules/core/module-orchestrator.js151
1 files changed, 151 insertions, 0 deletions
diff --git a/modules/core/module-orchestrator.js b/modules/core/module-orchestrator.js
new file mode 100644
index 0000000..4fab599
--- /dev/null
+++ b/modules/core/module-orchestrator.js
@@ -0,0 +1,151 @@
+// module-orchestrator.js โ€” Multi-agent orchestration system
+// Commands: orchestrate, orchestrator-status, orchestrator-spawn, orchestrator-kill, orchestrator-purge
+class OrchestratorModule {
+ constructor() {
+ this.commands = {};
+ this.terminal = null;
+ this._agents = {};
+ this._SPAWN_LIMIT = 3;
+ this._heartbeatInterval = null;
+ }
+ registerCommands() {
+ this.commands['orchestrate'] = (args) => this.delegateTask(args.join(' '));
+ this.commands['orchestrator-status'] = () => this.showStatus();
+ this.commands['orchestrator-spawn'] = (args) => this.spawnAgent(args[0], args.slice(1));
+ this.commands['orchestrator-kill'] = (args) => this.killAgent(args[0]);
+ this.commands['orchestrator-purge'] = () => this.purgeDeadAgents();
+ }
+ init(terminal) {
+ this.terminal = terminal;
+ // Register orchestrator in the agent registry
+ const registry = this.terminal.moduleManager.modules['AgentRegistryModule'];
+ if (registry) {
+ registry.registerAgent(['register', 'orchestrator-0', 'delegate', 'spawn', 'monitor', 'purge']);
+ }
+ // Heartbeat watchdog every 3 seconds
+ this._heartbeatInterval = setInterval(() => this._checkHeartbeats(), 3000);
+ this.terminal.print('๐Ÿง  Orchestrator loaded โ€” ready to delegate', 'success');
+ }
+ spawnAgent(role, capabilities) {
+ const id = `agent-${Object.keys(this._agents).length + 1}-${role || 'worker'}`;
+ if (Object.keys(this._agents).length >= this._SPAWN_LIMIT) {
+ this.terminal.print(`โŒ Agent spawn limit (${this._SPAWN_LIMIT}) reached`, 'error');
+ return null;
+ }
+ const agent = {
+ id,
+ role: role || 'worker',
+ capabilities: capabilities.length ? capabilities : ['list_files', 'read_file', 'web_search'],
+ createdAt: new Date().toISOString(),
+ lastHeartbeat: Date.now(),
+ status: 'idle',
+ currentTask: null
+ };
+ this._agents[id] = agent;
+ // Register in agent registry
+ const registry = this.terminal.moduleManager.modules['AgentRegistryModule'];
+ if (registry) registry.registerAgent(['register', id, ...agent.capabilities]);
+ this.terminal.print(`๐Ÿค– Agent '${id}' spawned (role: ${role || 'worker'}, ${agent.capabilities.length} caps)`, 'success');
+ return agent;
+ }
+ killAgent(id) {
+ if (!this._agents[id]) return this.terminal.print(`โŒ Agent '${id}' not found`, 'error');
+ // Revoke any active lease
+ const leaseModule = this.terminal.moduleManager.modules['LeaseModule'];
+ if (leaseModule) {
+ Object.values(leaseModule._leases).forEach(l => {
+ if (l.workerId === id && l.status === 'active') leaseModule.revokeLease(l.id);
+ });
+ }
+ // Unregister from registry
+ const registry = this.terminal.moduleManager.modules['AgentRegistryModule'];
+ if (registry) registry.unregisterAgent(id);
+ delete this._agents[id];
+ this.terminal.print(`๐Ÿ’€ Agent '${id}' killed`, 'warning');
+ }
+ async delegateTask(task) {
+ if (!task) return this.terminal.print('Usage: orchestrate <task description>', 'error');
+ // Find an idle agent or auto-spawn
+ let agent = Object.values(this._agents).find(a => a.status === 'idle');
+ if (!agent) {
+ if (Object.keys(this._agents).length >= this._SPAWN_LIMIT) {
+ return this.terminal.print('โŒ All agents busy and spawn limit reached', 'error');
+ }
+ this.spawnAgent('worker', ['list_files', 'read_file', 'write_file', 'web_search']);
+ agent = Object.values(this._agents).find(a => a.status === 'idle');
+ }
+ if (!agent) return this.terminal.print('โŒ No available agents', 'error');
+ // Create a lease
+ const leaseModule = this.terminal.moduleManager.modules['LeaseModule'];
+ const lease = leaseModule ? leaseModule.createLease(['lease-create', agent.id, '60', task]) : null;
+ // Mark agent as busy
+ agent.status = 'busy';
+ agent.currentTask = task;
+ agent.leaseId = lease?.id;
+ // Write task to agent's inbox
+ if (!this.terminal.fs.readFile(`/agents/${agent.id}/`)) this.terminal.fs.mkdir(`agents/${agent.id}`);
+ this.terminal.fs.writeFile(`agents/${agent.id}/inbox.json`, JSON.stringify({
+ task,
+ from: 'orchestrator-0',
+ leaseId: lease?.id,
+ timestamp: new Date().toISOString(),
+ ttl_seconds: 60
+ }));
+ this.terminal.print(`๐ŸŽฏ Delegated "${task.substring(0, 60)}" to ${agent.id} (lease: ${lease?.id || 'none'})`, 'info');
+ // Execute via AI assistant
+ try {
+ const aiModule = this.terminal.moduleManager.modules['WebX11AIAssistantModule'];
+ if (aiModule && typeof aiModule.chat === 'function') {
+ const result = await aiModule.chat([`[DELEGATED] ${task}`]);
+ agent.status = 'completed';
+ this.terminal.fs.writeFile(`agents/${agent.id}/outbox.json`, JSON.stringify({
+ result: result || 'completed',
+ status: 'completed',
+ timestamp: new Date().toISOString()
+ }));
+ this.terminal.print(`โœ… ${agent.id} completed task`, 'success');
+ } else {
+ this.terminal.print(`โ„น๏ธ AI module not available โ€” task queued for ${agent.id}`, 'info');
+ }
+ } catch (e) {
+ agent.status = 'failed';
+ this.terminal.print(`โŒ ${agent.id} failed: ${e.message}`, 'error');
+ }
+ // Return to idle pool
+ agent.status = 'idle';
+ agent.currentTask = null;
+ agent.lastHeartbeat = Date.now();
+ }
+ _checkHeartbeats() {
+ const now = Date.now();
+ Object.entries(this._agents).forEach(([id, agent]) => {
+ if (agent.status === 'busy' && now - agent.lastHeartbeat > 15000) {
+ this.terminal.print(`โš ๏ธ Agent '${id}' unresponsive (${Math.round((now - agent.lastHeartbeat) / 1000)}s since heartbeat)`, 'warning');
+ }
+ });
+ }
+ purgeDeadAgents() {
+ const now = Date.now();
+ let count = 0;
+ Object.entries(this._agents).forEach(([id, agent]) => {
+ if (now - agent.lastHeartbeat > 60000) {
+ this.killAgent(id);
+ count++;
+ }
+ });
+ this.terminal.print(`๐Ÿงน Purged ${count} dead agents`, count > 0 ? 'warning' : 'info');
+ }
+ showStatus() {
+ const total = Object.keys(this._agents).length;
+ const busy = Object.values(this._agents).filter(a => a.status === 'busy').length;
+ const idle = Object.values(this._agents).filter(a => a.status === 'idle').length;
+ const failed = Object.values(this._agents).filter(a => a.status === 'failed').length;
+ this.terminal.print('๐Ÿง  Orchestrator Status', 'header');
+ this.terminal.print(` Agents: ${total} (${busy} busy, ${idle} idle, ${failed} failed)`, 'info');
+ this.terminal.print(` Spawn limit: ${this._SPAWN_LIMIT}`, 'info');
+ Object.values(this._agents).forEach(a => {
+ const task = a.currentTask ? ` (task: ${a.currentTask.substring(0, 40)})` : '';
+ this.terminal.print(` ${a.id} โ€” ${a.status}${task}`, 'info');
+ });
+ }
+} \ No newline at end of file