@supaku/agentfactory-cli 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/analyze-logs.d.ts +26 -0
- package/dist/src/analyze-logs.d.ts.map +1 -0
- package/dist/src/analyze-logs.js +317 -0
- package/dist/src/cleanup.d.ts +21 -0
- package/dist/src/cleanup.d.ts.map +1 -0
- package/dist/src/cleanup.js +309 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +16 -0
- package/dist/src/queue-admin.d.ts +24 -0
- package/dist/src/queue-admin.d.ts.map +1 -0
- package/dist/src/queue-admin.js +418 -0
- package/dist/src/worker-fleet.d.ts +23 -0
- package/dist/src/worker-fleet.d.ts.map +1 -0
- package/dist/src/worker-fleet.js +256 -0
- package/dist/src/worker.d.ts +10 -10
- package/dist/src/worker.js +734 -50
- package/package.json +11 -5
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AgentFactory Worker Fleet Manager
|
|
4
|
+
*
|
|
5
|
+
* Spawns and manages multiple worker processes for parallel agent execution.
|
|
6
|
+
* Each worker runs as a separate process with its own resources.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* af-worker-fleet [options]
|
|
10
|
+
*
|
|
11
|
+
* Options:
|
|
12
|
+
* -w, --workers <n> Number of worker processes (default: CPU cores / 2)
|
|
13
|
+
* -c, --capacity <n> Agents per worker (default: 3)
|
|
14
|
+
* --dry-run Show configuration without starting workers
|
|
15
|
+
*
|
|
16
|
+
* Environment (loaded from .env.local in CWD):
|
|
17
|
+
* WORKER_FLEET_SIZE Number of workers (override)
|
|
18
|
+
* WORKER_CAPACITY Agents per worker (override)
|
|
19
|
+
* WORKER_API_URL Coordinator API URL (required)
|
|
20
|
+
* WORKER_API_KEY API key for authentication (required)
|
|
21
|
+
*/
|
|
22
|
+
import { spawn } from 'child_process';
|
|
23
|
+
import os from 'os';
|
|
24
|
+
import path from 'path';
|
|
25
|
+
import { fileURLToPath } from 'url';
|
|
26
|
+
import { config as loadEnv } from 'dotenv';
|
|
27
|
+
// Load environment variables from .env.local in CWD
|
|
28
|
+
loadEnv({ path: path.resolve(process.cwd(), '.env.local') });
|
|
29
|
+
// Resolve the directory of this script (for finding worker.js)
|
|
30
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
31
|
+
const __dirname = path.dirname(__filename);
|
|
32
|
+
// ANSI colors
|
|
33
|
+
const colors = {
|
|
34
|
+
reset: '\x1b[0m',
|
|
35
|
+
red: '\x1b[31m',
|
|
36
|
+
green: '\x1b[32m',
|
|
37
|
+
yellow: '\x1b[33m',
|
|
38
|
+
blue: '\x1b[34m',
|
|
39
|
+
magenta: '\x1b[35m',
|
|
40
|
+
cyan: '\x1b[36m',
|
|
41
|
+
gray: '\x1b[90m',
|
|
42
|
+
};
|
|
43
|
+
// Worker color cycling
|
|
44
|
+
const workerColors = [
|
|
45
|
+
colors.cyan,
|
|
46
|
+
colors.magenta,
|
|
47
|
+
colors.yellow,
|
|
48
|
+
colors.green,
|
|
49
|
+
colors.blue,
|
|
50
|
+
];
|
|
51
|
+
function parseArgs() {
|
|
52
|
+
const args = process.argv.slice(2);
|
|
53
|
+
let workers = parseInt(process.env.WORKER_FLEET_SIZE ?? '0', 10) || Math.max(1, Math.floor(os.cpus().length / 2));
|
|
54
|
+
let capacity = parseInt(process.env.WORKER_CAPACITY ?? '3', 10);
|
|
55
|
+
let dryRun = false;
|
|
56
|
+
for (let i = 0; i < args.length; i++) {
|
|
57
|
+
if (args[i] === '--workers' || args[i] === '-w') {
|
|
58
|
+
workers = parseInt(args[++i], 10);
|
|
59
|
+
}
|
|
60
|
+
else if (args[i] === '--capacity' || args[i] === '-c') {
|
|
61
|
+
capacity = parseInt(args[++i], 10);
|
|
62
|
+
}
|
|
63
|
+
else if (args[i] === '--dry-run') {
|
|
64
|
+
dryRun = true;
|
|
65
|
+
}
|
|
66
|
+
else if (args[i] === '--help' || args[i] === '-h') {
|
|
67
|
+
printHelp();
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { workers, capacity, dryRun };
|
|
72
|
+
}
|
|
73
|
+
function printHelp() {
|
|
74
|
+
console.log(`
|
|
75
|
+
${colors.cyan}AgentFactory Worker Fleet Manager${colors.reset}
|
|
76
|
+
Spawns and manages multiple worker processes for parallel agent execution.
|
|
77
|
+
|
|
78
|
+
${colors.yellow}Usage:${colors.reset}
|
|
79
|
+
af-worker-fleet [options]
|
|
80
|
+
|
|
81
|
+
${colors.yellow}Options:${colors.reset}
|
|
82
|
+
-w, --workers <n> Number of worker processes (default: CPU cores / 2)
|
|
83
|
+
-c, --capacity <n> Agents per worker (default: 3)
|
|
84
|
+
--dry-run Show configuration without starting workers
|
|
85
|
+
-h, --help Show this help message
|
|
86
|
+
|
|
87
|
+
${colors.yellow}Examples:${colors.reset}
|
|
88
|
+
af-worker-fleet # Auto-detect optimal settings
|
|
89
|
+
af-worker-fleet -w 8 -c 5 # 8 workers x 5 agents = 40 concurrent
|
|
90
|
+
af-worker-fleet --workers 16 # 16 workers with default capacity
|
|
91
|
+
|
|
92
|
+
${colors.yellow}Environment (loaded from .env.local in CWD):${colors.reset}
|
|
93
|
+
WORKER_FLEET_SIZE Override number of workers
|
|
94
|
+
WORKER_CAPACITY Override agents per worker
|
|
95
|
+
WORKER_API_URL API endpoint (required)
|
|
96
|
+
WORKER_API_KEY API key for authentication (required)
|
|
97
|
+
|
|
98
|
+
${colors.yellow}System Info:${colors.reset}
|
|
99
|
+
CPU Cores: ${os.cpus().length}
|
|
100
|
+
Total RAM: ${Math.round(os.totalmem() / 1024 / 1024 / 1024)} GB
|
|
101
|
+
Free RAM: ${Math.round(os.freemem() / 1024 / 1024 / 1024)} GB
|
|
102
|
+
`);
|
|
103
|
+
}
|
|
104
|
+
function timestamp() {
|
|
105
|
+
return new Date().toLocaleTimeString('en-US', { hour12: false });
|
|
106
|
+
}
|
|
107
|
+
function fleetLog(workerId, color, level, message) {
|
|
108
|
+
const prefix = workerId !== null ? `[W${workerId.toString().padStart(2, '0')}]` : '[FLEET]';
|
|
109
|
+
const levelColor = level === 'ERR' ? colors.red : level === 'WRN' ? colors.yellow : colors.gray;
|
|
110
|
+
console.log(`${colors.gray}${timestamp()}${colors.reset} ${color}${prefix}${colors.reset} ${levelColor}${level}${colors.reset} ${message}`);
|
|
111
|
+
}
|
|
112
|
+
class WorkerFleet {
|
|
113
|
+
workers = new Map();
|
|
114
|
+
fleetConfig;
|
|
115
|
+
shuttingDown = false;
|
|
116
|
+
workerScript;
|
|
117
|
+
constructor(fleetConfig) {
|
|
118
|
+
this.fleetConfig = fleetConfig;
|
|
119
|
+
// Use the compiled worker.js in the same directory
|
|
120
|
+
this.workerScript = path.resolve(__dirname, 'worker.js');
|
|
121
|
+
}
|
|
122
|
+
async start() {
|
|
123
|
+
const { workers, capacity, dryRun } = this.fleetConfig;
|
|
124
|
+
const totalCapacity = workers * capacity;
|
|
125
|
+
console.log(`
|
|
126
|
+
${colors.cyan}================================================================${colors.reset}
|
|
127
|
+
${colors.cyan} AgentFactory Worker Fleet Manager${colors.reset}
|
|
128
|
+
${colors.cyan}================================================================${colors.reset}
|
|
129
|
+
Workers: ${colors.green}${workers}${colors.reset}
|
|
130
|
+
Capacity/Worker: ${colors.green}${capacity}${colors.reset}
|
|
131
|
+
Total Capacity: ${colors.green}${totalCapacity}${colors.reset} concurrent agents
|
|
132
|
+
|
|
133
|
+
System:
|
|
134
|
+
CPU Cores: ${os.cpus().length}
|
|
135
|
+
Total RAM: ${Math.round(os.totalmem() / 1024 / 1024 / 1024)} GB
|
|
136
|
+
Free RAM: ${Math.round(os.freemem() / 1024 / 1024 / 1024)} GB
|
|
137
|
+
${colors.cyan}================================================================${colors.reset}
|
|
138
|
+
`);
|
|
139
|
+
if (dryRun) {
|
|
140
|
+
console.log(`${colors.yellow}Dry run mode - not starting workers${colors.reset}`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Set up shutdown handlers
|
|
144
|
+
process.on('SIGINT', () => this.shutdown('SIGINT'));
|
|
145
|
+
process.on('SIGTERM', () => this.shutdown('SIGTERM'));
|
|
146
|
+
// Spawn workers with staggered start to avoid thundering herd
|
|
147
|
+
for (let i = 0; i < workers; i++) {
|
|
148
|
+
await this.spawnWorker(i);
|
|
149
|
+
if (i < workers - 1) {
|
|
150
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
fleetLog(null, colors.green, 'INF', `All ${workers} workers started`);
|
|
154
|
+
// Keep the fleet manager running
|
|
155
|
+
await new Promise(() => { });
|
|
156
|
+
}
|
|
157
|
+
async spawnWorker(id) {
|
|
158
|
+
const color = workerColors[id % workerColors.length];
|
|
159
|
+
const existingWorker = this.workers.get(id);
|
|
160
|
+
const restartCount = existingWorker?.restartCount ?? 0;
|
|
161
|
+
fleetLog(id, color, 'INF', `Starting worker (capacity: ${this.fleetConfig.capacity})${restartCount > 0 ? ` [restart #${restartCount}]` : ''}`);
|
|
162
|
+
const workerProcess = spawn('node', [this.workerScript, '--capacity', String(this.fleetConfig.capacity)], {
|
|
163
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
164
|
+
env: {
|
|
165
|
+
...process.env,
|
|
166
|
+
WORKER_FLEET_ID: String(id),
|
|
167
|
+
},
|
|
168
|
+
cwd: process.cwd(),
|
|
169
|
+
});
|
|
170
|
+
const workerInfo = {
|
|
171
|
+
id,
|
|
172
|
+
process: workerProcess,
|
|
173
|
+
color,
|
|
174
|
+
startedAt: new Date(),
|
|
175
|
+
restartCount,
|
|
176
|
+
};
|
|
177
|
+
this.workers.set(id, workerInfo);
|
|
178
|
+
// Handle stdout - prefix with worker ID
|
|
179
|
+
workerProcess.stdout?.on('data', (data) => {
|
|
180
|
+
const lines = data.toString().trim().split('\n');
|
|
181
|
+
for (const line of lines) {
|
|
182
|
+
if (line.trim()) {
|
|
183
|
+
console.log(`${color}[W${id.toString().padStart(2, '0')}]${colors.reset} ${line}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
// Handle stderr
|
|
188
|
+
workerProcess.stderr?.on('data', (data) => {
|
|
189
|
+
const lines = data.toString().trim().split('\n');
|
|
190
|
+
for (const line of lines) {
|
|
191
|
+
if (line.trim()) {
|
|
192
|
+
console.log(`${color}[W${id.toString().padStart(2, '0')}]${colors.reset} ${colors.red}${line}${colors.reset}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
// Handle worker exit
|
|
197
|
+
workerProcess.on('exit', (code, signal) => {
|
|
198
|
+
if (this.shuttingDown) {
|
|
199
|
+
fleetLog(id, color, 'INF', `Worker stopped (code: ${code}, signal: ${signal})`);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
fleetLog(id, color, 'WRN', `Worker exited unexpectedly (code: ${code}, signal: ${signal}) - restarting in 5s`);
|
|
203
|
+
const worker = this.workers.get(id);
|
|
204
|
+
if (worker) {
|
|
205
|
+
worker.restartCount++;
|
|
206
|
+
}
|
|
207
|
+
setTimeout(() => {
|
|
208
|
+
if (!this.shuttingDown) {
|
|
209
|
+
this.spawnWorker(id);
|
|
210
|
+
}
|
|
211
|
+
}, 5000);
|
|
212
|
+
});
|
|
213
|
+
workerProcess.on('error', (err) => {
|
|
214
|
+
fleetLog(id, color, 'ERR', `Worker error: ${err.message}`);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
async shutdown(signal) {
|
|
218
|
+
if (this.shuttingDown)
|
|
219
|
+
return;
|
|
220
|
+
this.shuttingDown = true;
|
|
221
|
+
console.log(`\n${colors.yellow}Received ${signal} - shutting down fleet...${colors.reset}`);
|
|
222
|
+
for (const [id, worker] of this.workers) {
|
|
223
|
+
fleetLog(id, worker.color, 'INF', 'Stopping worker...');
|
|
224
|
+
worker.process.kill('SIGTERM');
|
|
225
|
+
}
|
|
226
|
+
// Wait for workers to exit (max 30 seconds)
|
|
227
|
+
const timeout = setTimeout(() => {
|
|
228
|
+
console.log(`${colors.red}Timeout waiting for workers - force killing${colors.reset}`);
|
|
229
|
+
for (const worker of this.workers.values()) {
|
|
230
|
+
worker.process.kill('SIGKILL');
|
|
231
|
+
}
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}, 30000);
|
|
234
|
+
await Promise.all(Array.from(this.workers.values()).map((worker) => new Promise((resolve) => {
|
|
235
|
+
worker.process.on('exit', () => resolve());
|
|
236
|
+
})));
|
|
237
|
+
clearTimeout(timeout);
|
|
238
|
+
console.log(`${colors.green}All workers stopped${colors.reset}`);
|
|
239
|
+
process.exit(0);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Main
|
|
243
|
+
const fleetConfig = parseArgs();
|
|
244
|
+
if (!process.env.WORKER_API_URL) {
|
|
245
|
+
console.error(`${colors.red}Error: WORKER_API_URL environment variable is required${colors.reset}`);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
if (!process.env.WORKER_API_KEY) {
|
|
249
|
+
console.error(`${colors.red}Error: WORKER_API_KEY environment variable is required${colors.reset}`);
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
const fleet = new WorkerFleet(fleetConfig);
|
|
253
|
+
fleet.start().catch((err) => {
|
|
254
|
+
console.error(`${colors.red}Fleet error: ${err instanceof Error ? err.message : String(err)}${colors.reset}`);
|
|
255
|
+
process.exit(1);
|
|
256
|
+
});
|
package/dist/src/worker.d.ts
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* AgentFactory Worker CLI
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* and processes assigned agent sessions.
|
|
5
|
+
* Local worker that polls the coordinator for work and executes agents.
|
|
7
6
|
*
|
|
8
7
|
* Usage:
|
|
9
8
|
* af-worker [options]
|
|
10
9
|
*
|
|
11
10
|
* Options:
|
|
12
|
-
* --capacity <number> Maximum concurrent agents (default:
|
|
13
|
-
* --
|
|
14
|
-
* --api-
|
|
11
|
+
* --capacity <number> Maximum concurrent agents (default: 3)
|
|
12
|
+
* --hostname <name> Worker hostname (default: os.hostname())
|
|
13
|
+
* --api-url <url> Coordinator API URL (default: WORKER_API_URL env)
|
|
14
|
+
* --api-key <key> API key (default: WORKER_API_KEY env)
|
|
15
|
+
* --dry-run Poll but don't execute work
|
|
15
16
|
*
|
|
16
|
-
* Environment:
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* WORKER_API_KEY API key (alternative to --api-key)
|
|
17
|
+
* Environment (loaded from .env.local in CWD):
|
|
18
|
+
* WORKER_API_URL Coordinator API URL (e.g., https://agent.example.com)
|
|
19
|
+
* WORKER_API_KEY API key for authentication
|
|
20
|
+
* LINEAR_API_KEY Required for agent operations
|
|
21
21
|
*/
|
|
22
22
|
export {};
|
|
23
23
|
//# sourceMappingURL=worker.d.ts.map
|