diff options
| author | Agando <roduccomm@gmail.com> | 2026-07-09 06:00:32 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-07-09 06:00:32 +0200 |
| commit | 2538c91876455fe96febf441fc8dde20f2606023 (patch) | |
| tree | b74c949cdf4296d10103dd57ef8178ba427a5b39 /modules/core/module-orchestrator.js | |
| parent | ea4d09fb5ccba12ed774fcf7c913edfa1e6a851a (diff) | |
auto: 2026-07-09_06:00 changes
Diffstat (limited to 'modules/core/module-orchestrator.js')
| -rw-r--r-- | modules/core/module-orchestrator.js | 140 |
1 files changed, 129 insertions, 11 deletions
diff --git a/modules/core/module-orchestrator.js b/modules/core/module-orchestrator.js index ba43dfd..59d9136 100644 --- a/modules/core/module-orchestrator.js +++ b/modules/core/module-orchestrator.js @@ -32,6 +32,8 @@ class OrchestratorModule { this._activeConsensus = null; this._pipelineHistory = []; this._PIPELINE_CONFIG = { autoFix: false, stageTimeout: 30000 }; + this._schedules = []; + this._scheduleTimer = null; this._loadConfig(); } _loadConfig() { @@ -84,6 +86,9 @@ class OrchestratorModule { this.commands['consensus-status'] = () => this.consensusStatus(); this.commands['consensus-history'] = () => this.consensusHistory(); this.commands['pipeline'] = (args) => this.runPipeline(args); + this.commands['orchestrator-schedule'] = (args) => this.addSchedule(args); + this.commands['orchestrator-schedules'] = () => this.listSchedules(); + this.commands['orchestrator-unschedule'] = (args) => this.removeSchedule(args[0]); } // ─── Task Decomposition Engine ─── _decomposeGoal(goal) { @@ -146,6 +151,9 @@ class OrchestratorModule { 'consensus-status': 'consensus-status — Show current/active consensus round status', 'consensus-history': 'consensus-history — Show past consensus rounds and their resolutions', 'pipeline': 'pipeline <test|deploy|verify|history|config> — Autonomous CI/CD: test agents/queue/modules, deploy (test→auto-fix→verify), history (show runs), config (show config)', + 'orchestrator-schedule': 'orchestrator-schedule <role> <interval_seconds> <task...> — Schedule a recurring task delegation', + 'orchestrator-schedules': 'orchestrator-schedules — List all active recurring schedules', + 'orchestrator-unschedule': 'orchestrator-unschedule <id> — Remove a recurring schedule', 'orchestrator-status': 'orchestrator-status — Show agent pool status and config', 'orchestrator-spawn': 'orchestrator-spawn [role] [capabilities...] — Manually spawn a new agent', 'orchestrator-kill': 'orchestrator-kill <agent-id> — Terminate an agent and revoke leases', @@ -170,13 +178,17 @@ class OrchestratorModule { this._reanimateFromVFS(); // A2A mailbox polling loop (500ms) if (this._A2A_POLLING) { - this._a2aPollTimer = setInterval(() => this._pollAgentMailboxes(), this._A2A_POLL_INTERVAL); + this._a2aPollTimer = this.terminal.moduleManager._setInterval(() => this._pollAgentMailboxes(), this._A2A_POLL_INTERVAL); } // Heartbeat watchdog - this._heartbeatInterval = setInterval(() => this._checkHeartbeats(), this._HEARTBEAT_CHECK); + this._heartbeatInterval = this.terminal.moduleManager._setInterval(() => this._checkHeartbeats(), this._HEARTBEAT_CHECK); // Idle timeout sweep - this._idleSweepInterval = setInterval(() => this._sweepIdle(), this._IDLE_SWEEP_INTERVAL); + this._idleSweepInterval = this.terminal.moduleManager._setInterval(() => this._sweepIdle(), this._IDLE_SWEEP_INTERVAL); this.terminal.print('🧠 Orchestrator loaded — A2A mailbox transport active', 'success'); + // Load recurring schedules from VFS + this._loadSchedules(); + // Pre-spawn default agents after a short delay (let other modules init first) + setTimeout(() => this._preSpawnDefaultAgents(), 2000); // Subscribe to event bus for in-memory A2A delivery (instant, no VFS poll) this._on('a2a:message', (msg) => { if (msg.type === 'heartbeat' && msg.from && this._agents[msg.from]) { @@ -561,16 +573,16 @@ class OrchestratorModule { 'a2a-polling': () => { this._A2A_POLLING = val === 'on' || val === 'true'; if (this._A2A_POLLING && !this._a2aPollTimer) { - this._a2aPollTimer = setInterval(() => this._pollAgentMailboxes(), this._A2A_POLL_INTERVAL); + this._a2aPollTimer = this.terminal.moduleManager._setInterval(() => this._pollAgentMailboxes(), this._A2A_POLL_INTERVAL); } else if (!this._A2A_POLLING && this._a2aPollTimer) { - clearInterval(this._a2aPollTimer); + this.terminal.moduleManager._clearTimer(this._a2aPollTimer); this._a2aPollTimer = null; } return `a2a-polling ${this._A2A_POLLING ? 'on' : 'off'}`; }, - 'a2a-poll-interval': () => { const n = parseInt(val); if (isNaN(n) || n < 50 || n > 30000) return 'Must be 50-30000ms'; clearInterval(this._a2aPollTimer); this._a2aPollTimer = null; this._A2A_POLL_INTERVAL = n; if (this._A2A_POLLING) { this._a2aPollTimer = setInterval(() => this._pollAgentMailboxes(), n); } return `a2a-poll-interval set to ${n}ms`; }, - 'heartbeat-check': () => { const n = parseInt(val); if (isNaN(n) || n < 500 || n > 60000) return 'Must be 500-60000ms'; clearInterval(this._heartbeatInterval); this._HEARTBEAT_CHECK = n; this._heartbeatInterval = setInterval(() => this._checkHeartbeats(), n); return `heartbeat-check set to ${n}ms`; }, - 'idle-sweep-interval': () => { const n = parseInt(val); if (isNaN(n) || n < 5000 || n > 600000) return 'Must be 5000-600000ms'; clearInterval(this._idleSweepInterval); this._IDLE_SWEEP_INTERVAL = n; this._idleSweepInterval = setInterval(() => this._sweepIdle(), n); return `idle-sweep-interval set to ${n}ms`; }, + 'a2a-poll-interval': () => { const n = parseInt(val); if (isNaN(n) || n < 50 || n > 30000) return 'Must be 50-30000ms'; this.terminal.moduleManager._clearTimer(this._a2aPollTimer); this._a2aPollTimer = null; this._A2A_POLL_INTERVAL = n; if (this._A2A_POLLING) { this._a2aPollTimer = this.terminal.moduleManager._setInterval(() => this._pollAgentMailboxes(), n); } return `a2a-poll-interval set to ${n}ms`; }, + 'heartbeat-check': () => { const n = parseInt(val); if (isNaN(n) || n < 500 || n > 60000) return 'Must be 500-60000ms'; this.terminal.moduleManager._clearTimer(this._heartbeatInterval); this._HEARTBEAT_CHECK = n; this._heartbeatInterval = this.terminal.moduleManager._setInterval(() => this._checkHeartbeats(), n); return `heartbeat-check set to ${n}ms`; }, + 'idle-sweep-interval': () => { const n = parseInt(val); if (isNaN(n) || n < 5000 || n > 600000) return 'Must be 5000-600000ms'; this.terminal.moduleManager._clearTimer(this._idleSweepInterval); this._IDLE_SWEEP_INTERVAL = n; this._idleSweepInterval = this.terminal.moduleManager._setInterval(() => this._sweepIdle(), n); return `idle-sweep-interval set to ${n}ms`; }, 'dead-purge-timeout': () => { const n = parseInt(val); if (isNaN(n) || n < 5000 || n > 600000) return 'Must be 5000-600000ms'; this._DEAD_PURGE_TIMEOUT = n; return `dead-purge-timeout set to ${n}ms`; }, 'consensus-size': () => { const n = parseInt(val); if (isNaN(n) || n < 2 || n > 10) return 'Must be 2-10'; this._CONSENSUS_SIZE = n; return `consensus-size set to ${n}`; }, 'consensus-strategy': () => { if (!['majority', 'unanimous', 'weighted'].includes(val)) return 'Must be majority, unanimous, or weighted'; this._CONSENSUS_STRATEGY = val; return `consensus-strategy set to ${val}`; }, @@ -983,7 +995,7 @@ class OrchestratorModule { // Fix 2: Restart A2A poller if stopped if (!this._A2A_POLLING) { this._A2A_POLLING = true; - this._a2aPollTimer = setInterval(() => this._pollAgentMailboxes(), this._A2A_POLL_INTERVAL); + this._a2aPollTimer = this.terminal.moduleManager._setInterval(() => this._pollAgentMailboxes(), this._A2A_POLL_INTERVAL); this.terminal.print(' Restarted A2A polling', 'warning'); fixed++; } @@ -1113,9 +1125,12 @@ class OrchestratorModule { } } - // Create a lease + // Create a lease — with --write if agent has write capabilities const leaseModule = this.terminal.moduleManager.modules['LeaseModule']; - const lease = leaseModule ? leaseModule.createLease(['lease-create', agent.id, '60', task]) : null; + const hasWriteCap = (agent.capabilities || []).some(function(c) { return c === 'write_file' || c === 'edit_module' || c === 'delete_file'; }); + const leaseArgs = ['lease-create', agent.id, '60', task]; + if (hasWriteCap) leaseArgs.push('--write'); + const lease = leaseModule ? leaseModule.createLease(leaseArgs) : null; // Mark agent as busy agent.status = 'busy'; @@ -1184,6 +1199,109 @@ class OrchestratorModule { return outboxResult || 'completed'; } + // ─── Recurring Scheduling ─── + + addSchedule(args) { + if (args.length < 2) return this.terminal.print('Usage: orchestrator-schedule <role> <interval_seconds> <task...>', 'error'); + const role = args[0]; + const interval = parseInt(args[1]); + if (isNaN(interval) || interval < 10) return this.terminal.print('Interval must be >= 10 seconds', 'error'); + const task = args.slice(2).join(' '); + if (!task) return this.terminal.print('Task description required', 'error'); + + const schedule = { + id: 'sched-' + Date.now(), + role: role, + interval: interval * 1000, + task: task, + createdAt: new Date().toISOString(), + lastRun: null, + nextRun: Date.now() + interval * 1000, + runs: 0 + }; + this._schedules.push(schedule); + this._saveSchedules(); + this._startScheduleTimer(); + this.terminal.print('📅 Schedule created: "' + task.substring(0, 40) + '" every ' + interval + 's (role: ' + role + ')', 'success'); + return schedule; + } + + listSchedules() { + if (this._schedules.length === 0) return this.terminal.print('No active schedules', 'info'); + this.terminal.print('📅 Recurring Schedules (' + this._schedules.length + '):', 'header'); + for (const s of this._schedules) { + const nextIn = Math.max(0, Math.round((s.nextRun - Date.now()) / 1000)); + this.terminal.print(' ' + s.id + ' — "' + s.task.substring(0, 40) + '" every ' + (s.interval / 1000) + 's [' + s.role + '] (next: ' + nextIn + 's, runs: ' + s.runs + ')', 'info'); + } + } + + removeSchedule(id) { + const idx = this._schedules.findIndex(function(s) { return s.id === id; }); + if (idx === -1) return this.terminal.print('Schedule ' + id + ' not found', 'error'); + this._schedules.splice(idx, 1); + this._saveSchedules(); + if (this._schedules.length === 0 && this._scheduleTimer) { + clearInterval(this._scheduleTimer); + this._scheduleTimer = null; + } + this.terminal.print('Schedule ' + id + ' removed', 'success'); + } + + _startScheduleTimer() { + if (this._scheduleTimer) return; + this._scheduleTimer = setInterval(function() { + var now = Date.now(); + for (var i = 0; i < this._schedules.length; i++) { + var s = this._schedules[i]; + if (now >= s.nextRun) { + s.lastRun = new Date().toISOString(); + s.nextRun = now + s.interval; + s.runs++; + this.terminal.print('📅 Schedule trigger: "' + s.task.substring(0, 40) + '"', 'info'); + this.delegateTask(s.task); + } + } + this._saveSchedules(); + }.bind(this), 5000); + } + + _saveSchedules() { + try { + this.terminal.fs.fsWriteJSON('/orchestrator/schedules.json', this._schedules); + } catch (e) { console.warn('[Orchestrator] Schedule save error:', e.message); } + } + + _loadSchedules() { + try { + const data = this.terminal.fs.fsReadJSON('/orchestrator/schedules.json'); + if (data && Array.isArray(data)) { + this._schedules = data; + if (this._schedules.length > 0) this._startScheduleTimer(); + } + } catch (e) { this._schedules = []; } + } + + _preSpawnDefaultAgents() { + // Spawn 3 role-specific agents at boot + var roles = [ + { role: 'auto-learner', caps: ['list_files', 'read_file', 'write_file', 'view_module'], task: 'audit skills' }, + { role: 'kernel-architect', caps: ['list_files', 'read_file', 'write_file', 'run_command'], task: 'index VFS' }, + { role: 'searcher', caps: ['list_files', 'read_file', 'web_search'], task: 'check stale data' } + ]; + for (var i = 0; i < roles.length; i++) { + var r = roles[i]; + // Check if an agent with this role already exists + var existing = Object.values(this._agents).find(function(a) { return a.role === r.role; }); + if (!existing) { + var agent = this.spawnAgent(r.role, r.caps); + if (agent) { + agent.restartPolicy = 'always'; + this.terminal.print('🤖 Pre-spawned agent: ' + r.role + ' (' + r.caps.length + ' caps)', 'info'); + } + } + } + } + // ─── Monitoring ─── _checkHeartbeats() { |