1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
// module-leases.js — Lease system for agent task delegation
// Commands: lease-create, lease-list, lease-revoke, lease-renew
class LeaseModule {
constructor() {
this.commands = {};
this.terminal = null;
this._leases = {};
this._watchdogTimer = null;
}
registerCommands() {
this.commands['lease-create'] = (args) => this.createLease(args);
this.commands['lease-list'] = () => this.listLeases();
this.commands['lease-revoke'] = (args) => this.revokeLease(args[0]);
this.commands['lease-renew'] = (args) => this.renewLease(args[0], parseInt(args[1]) || 30);
}
init(terminal) {
this.terminal = terminal;
if (!this.terminal.fs.readFile('/leases/')) {
this.terminal.fs.mkdir('leases');
}
// Restore persisted leases
const files = this.terminal.fs.ls('/leases/');
if (files) {
files.forEach(f => {
if (f.name.endsWith('.json')) {
const data = this.terminal.fs.readFile(`/leases/${f.name}`);
if (data) {
try {
const lease = JSON.parse(data);
this._leases[lease.id] = lease;
} catch (e) {}
}
}
});
}
// Start watchdog every 5 seconds
this._watchdogTimer = setInterval(() => this._checkExpired(), 5000);
this.terminal.print('📜 Lease system loaded', 'info');
}
createLease(args) {
const workerId = args[1];
const duration = parseInt(args[2]) || 30;
const task = args.slice(3).join(' ') || 'generic task';
if (!workerId) return this.terminal.print('Usage: lease-create <agent-id> [duration_sec] [task]', 'error');
const lease = {
id: `lease-${Date.now()}`,
workerId,
orchestratorId: 'orchestrator-0',
task,
createdAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + duration * 1000).toISOString(),
status: 'active',
duration
};
this._leases[lease.id] = lease;
this.terminal.fs.writeFile(`leases/${lease.id}.json`, JSON.stringify(lease));
// Post A2A message to lease system agent
try {
const registry = this.terminal.moduleManager.modules['AgentRegistryModule'];
if (registry && typeof registry.a2aPost === 'function') {
registry.a2aPost('orchestrator-0', 'lease-system', 'lease.create', 'leases.create', {
leaseId: lease.id,
workerId,
task,
duration,
expiresAt: lease.expiresAt
});
}
} catch (e) {}
this.terminal.print(`📜 Lease ${lease.id} created for ${workerId} (${duration}s, task: ${task.substring(0, 40)})`, 'success');
return lease;
}
listLeases() {
const all = Object.values(this._leases);
const active = all.filter(l => l.status === 'active');
const expired = all.filter(l => l.status === 'expired');
const revoked = all.filter(l => l.status === 'revoked');
this.terminal.print(`📜 Leases: ${active.length} active, ${expired.length} expired, ${revoked.length} revoked`, 'header');
active.forEach(l => {
const remaining = Math.max(0, Math.round((new Date(l.expiresAt) - Date.now()) / 1000));
this.terminal.print(` ${l.id} → ${l.workerId} (${remaining}s remain) — ${l.task.substring(0, 40)}`, 'info');
});
}
revokeLease(id) {
if (!this._leases[id]) return this.terminal.print(`❌ Lease ${id} not found`, 'error');
this._leases[id].status = 'revoked';
this._leases[id].revokedAt = new Date().toISOString();
this.terminal.fs.writeFile(`leases/${id}.json`, JSON.stringify(this._leases[id]));
// Post A2A completion message
try {
const registry = this.terminal.moduleManager.modules['AgentRegistryModule'];
if (registry && typeof registry.a2aPost === 'function') {
registry.a2aPost('orchestrator-0', 'lease-system', 'lease.terminate', 'leases.terminate', {
leaseId: id,
status: 'revoked',
revokedAt: this._leases[id].revokedAt,
workerId: this._leases[id].workerId
});
}
} catch (e) {}
this.terminal.print(`✅ Lease ${id} revoked`, 'warning');
}
renewLease(id, additionalSeconds) {
if (!this._leases[id] || this._leases[id].status !== 'active') {
return this.terminal.print(`❌ Lease ${id} not found or not active`, 'error');
}
const lease = this._leases[id];
const newExpiry = new Date(Date.now() + additionalSeconds * 1000);
lease.expiresAt = newExpiry.toISOString();
lease.duration += additionalSeconds;
lease.renewedAt = new Date().toISOString();
this.terminal.fs.writeFile(`leases/${id}.json`, JSON.stringify(lease));
this.terminal.print(`✅ Lease ${id} renewed (+${additionalSeconds}s)`, 'success');
}
_checkExpired() {
const now = Date.now();
Object.values(this._leases).forEach(lease => {
if (lease.status === 'active' && new Date(lease.expiresAt) < now) {
lease.status = 'expired';
lease.expiredAt = new Date().toISOString();
this.terminal.fs.writeFile(`leases/${lease.id}.json`, JSON.stringify(lease));
this.terminal.log('INFO', 'Lease', `Lease ${lease.id} for ${lease.workerId} expired`);
}
});
}
}
|