@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
package/dist/src/index.js
CHANGED
|
@@ -17,6 +17,10 @@ Usage:
|
|
|
17
17
|
Commands:
|
|
18
18
|
orchestrator Spawn concurrent agents on backlog issues
|
|
19
19
|
worker Start a remote worker that polls for queued work
|
|
20
|
+
worker-fleet Spawn and manage multiple worker processes
|
|
21
|
+
cleanup Clean up orphaned git worktrees
|
|
22
|
+
queue-admin Manage Redis work queue and sessions
|
|
23
|
+
analyze-logs Analyze agent session logs for errors
|
|
20
24
|
help Show this help message
|
|
21
25
|
|
|
22
26
|
Run 'agentfactory <command> --help' for command-specific options.
|
|
@@ -31,6 +35,18 @@ switch (command) {
|
|
|
31
35
|
case 'worker':
|
|
32
36
|
import('./worker');
|
|
33
37
|
break;
|
|
38
|
+
case 'worker-fleet':
|
|
39
|
+
import('./worker-fleet');
|
|
40
|
+
break;
|
|
41
|
+
case 'cleanup':
|
|
42
|
+
import('./cleanup');
|
|
43
|
+
break;
|
|
44
|
+
case 'queue-admin':
|
|
45
|
+
import('./queue-admin');
|
|
46
|
+
break;
|
|
47
|
+
case 'analyze-logs':
|
|
48
|
+
import('./analyze-logs');
|
|
49
|
+
break;
|
|
34
50
|
case 'help':
|
|
35
51
|
case '--help':
|
|
36
52
|
case '-h':
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AgentFactory Queue Admin
|
|
4
|
+
*
|
|
5
|
+
* Manage the Redis work queue, sessions, claims, and workers.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* af-queue-admin <command>
|
|
9
|
+
*
|
|
10
|
+
* Commands:
|
|
11
|
+
* list List all queued work items
|
|
12
|
+
* sessions List all sessions
|
|
13
|
+
* workers List all registered workers
|
|
14
|
+
* clear-claims Clear stale work claims
|
|
15
|
+
* clear-queue Clear the work queue
|
|
16
|
+
* clear-all Clear queue, sessions, claims, and workers
|
|
17
|
+
* reset Full state reset (claims + queue + stuck sessions)
|
|
18
|
+
* remove <id> Remove a specific session by ID (partial match)
|
|
19
|
+
*
|
|
20
|
+
* Environment (loaded from .env.local in CWD):
|
|
21
|
+
* REDIS_URL Required for Redis connection
|
|
22
|
+
*/
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=queue-admin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-admin.d.ts","sourceRoot":"","sources":["../../src/queue-admin.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;GAoBG"}
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AgentFactory Queue Admin
|
|
4
|
+
*
|
|
5
|
+
* Manage the Redis work queue, sessions, claims, and workers.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* af-queue-admin <command>
|
|
9
|
+
*
|
|
10
|
+
* Commands:
|
|
11
|
+
* list List all queued work items
|
|
12
|
+
* sessions List all sessions
|
|
13
|
+
* workers List all registered workers
|
|
14
|
+
* clear-claims Clear stale work claims
|
|
15
|
+
* clear-queue Clear the work queue
|
|
16
|
+
* clear-all Clear queue, sessions, claims, and workers
|
|
17
|
+
* reset Full state reset (claims + queue + stuck sessions)
|
|
18
|
+
* remove <id> Remove a specific session by ID (partial match)
|
|
19
|
+
*
|
|
20
|
+
* Environment (loaded from .env.local in CWD):
|
|
21
|
+
* REDIS_URL Required for Redis connection
|
|
22
|
+
*/
|
|
23
|
+
import path from 'path';
|
|
24
|
+
import { config } from 'dotenv';
|
|
25
|
+
// Load environment variables from .env.local in CWD
|
|
26
|
+
config({ path: path.resolve(process.cwd(), '.env.local') });
|
|
27
|
+
import { getRedisClient, redisKeys, redisDel, redisGet, redisSet, redisZRangeByScore, redisHGetAll, disconnectRedis, } from '@supaku/agentfactory-server';
|
|
28
|
+
// Redis key constants
|
|
29
|
+
const WORK_QUEUE_KEY = 'work:queue';
|
|
30
|
+
const WORK_ITEMS_KEY = 'work:items';
|
|
31
|
+
const WORK_CLAIM_PREFIX = 'work:claim:';
|
|
32
|
+
const SESSION_KEY_PREFIX = 'agent:session:';
|
|
33
|
+
const WORKER_PREFIX = 'work:worker:';
|
|
34
|
+
// ANSI colors
|
|
35
|
+
const C = {
|
|
36
|
+
reset: '\x1b[0m',
|
|
37
|
+
red: '\x1b[31m',
|
|
38
|
+
green: '\x1b[32m',
|
|
39
|
+
yellow: '\x1b[33m',
|
|
40
|
+
cyan: '\x1b[36m',
|
|
41
|
+
gray: '\x1b[90m',
|
|
42
|
+
};
|
|
43
|
+
function ensureRedis() {
|
|
44
|
+
if (!process.env.REDIS_URL) {
|
|
45
|
+
console.error(`${C.red}Error: REDIS_URL not set${C.reset}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
// Initialize the Redis client
|
|
49
|
+
getRedisClient();
|
|
50
|
+
}
|
|
51
|
+
async function listQueue() {
|
|
52
|
+
ensureRedis();
|
|
53
|
+
// Get items from sorted set (current queue format)
|
|
54
|
+
const queuedSessionIds = await redisZRangeByScore(WORK_QUEUE_KEY, '-inf', '+inf');
|
|
55
|
+
const workItems = await redisHGetAll(WORK_ITEMS_KEY);
|
|
56
|
+
const workItemCount = Object.keys(workItems).length;
|
|
57
|
+
console.log(`\n${C.cyan}Work Queue${C.reset} (${Math.max(queuedSessionIds.length, workItemCount)} items):`);
|
|
58
|
+
console.log('='.repeat(60));
|
|
59
|
+
if (workItemCount === 0 && queuedSessionIds.length === 0) {
|
|
60
|
+
console.log('(empty)');
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
for (const [sessionId, itemJson] of Object.entries(workItems)) {
|
|
64
|
+
try {
|
|
65
|
+
const work = JSON.parse(itemJson);
|
|
66
|
+
console.log(`- ${work.issueIdentifier ?? sessionId.slice(0, 8)} (session: ${sessionId.slice(0, 8)}...)`);
|
|
67
|
+
console.log(` Priority: ${work.priority ?? 'none'}, WorkType: ${work.workType ?? 'development'}`);
|
|
68
|
+
if (work.queuedAt) {
|
|
69
|
+
console.log(` Queued: ${new Date(work.queuedAt).toISOString()}`);
|
|
70
|
+
}
|
|
71
|
+
if (work.claudeSessionId) {
|
|
72
|
+
console.log(` ${C.yellow}Has claudeSessionId: ${work.claudeSessionId.substring(0, 12)}${C.reset}`);
|
|
73
|
+
}
|
|
74
|
+
if (work.prompt) {
|
|
75
|
+
console.log(` Prompt: "${work.prompt.slice(0, 50)}..."`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
console.log(`- [invalid JSON]: ${sessionId}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
await disconnectRedis();
|
|
84
|
+
}
|
|
85
|
+
async function listSessions() {
|
|
86
|
+
ensureRedis();
|
|
87
|
+
const keys = await redisKeys(`${SESSION_KEY_PREFIX}*`);
|
|
88
|
+
console.log(`\n${C.cyan}Sessions${C.reset} (${keys.length} total):`);
|
|
89
|
+
console.log('='.repeat(60));
|
|
90
|
+
if (keys.length === 0) {
|
|
91
|
+
console.log('(none)');
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
for (const key of keys) {
|
|
95
|
+
const session = await redisGet(key);
|
|
96
|
+
if (session) {
|
|
97
|
+
const statusColors = {
|
|
98
|
+
pending: C.yellow,
|
|
99
|
+
claimed: C.cyan,
|
|
100
|
+
running: C.cyan,
|
|
101
|
+
completed: C.green,
|
|
102
|
+
failed: C.red,
|
|
103
|
+
stopped: C.yellow,
|
|
104
|
+
};
|
|
105
|
+
const statusColor = statusColors[session.status] || '';
|
|
106
|
+
console.log(`- ${session.issueIdentifier || session.issueId.slice(0, 8)} [${statusColor}${session.status}${C.reset}]`);
|
|
107
|
+
console.log(` Session: ${session.linearSessionId.slice(0, 12)}...`);
|
|
108
|
+
if (session.workType) {
|
|
109
|
+
console.log(` WorkType: ${session.workType}`);
|
|
110
|
+
}
|
|
111
|
+
console.log(` Updated: ${new Date(session.updatedAt * 1000).toISOString()}`);
|
|
112
|
+
if (session.workerId) {
|
|
113
|
+
console.log(` Worker: ${session.workerId}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
await disconnectRedis();
|
|
119
|
+
}
|
|
120
|
+
async function listWorkersFn() {
|
|
121
|
+
ensureRedis();
|
|
122
|
+
const keys = await redisKeys(`${WORKER_PREFIX}*`);
|
|
123
|
+
console.log(`\n${C.cyan}Workers${C.reset} (${keys.length} total):`);
|
|
124
|
+
console.log('='.repeat(60));
|
|
125
|
+
if (keys.length === 0) {
|
|
126
|
+
console.log('(none)');
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
for (const key of keys) {
|
|
130
|
+
const worker = await redisGet(key);
|
|
131
|
+
if (worker) {
|
|
132
|
+
const statusColor = worker.status === 'active' ? C.green : C.yellow;
|
|
133
|
+
console.log(`- ${worker.id.slice(0, 12)} [${statusColor}${worker.status}${C.reset}]`);
|
|
134
|
+
if (worker.hostname) {
|
|
135
|
+
console.log(` Hostname: ${worker.hostname}`);
|
|
136
|
+
}
|
|
137
|
+
console.log(` Capacity: ${worker.activeCount ?? 0}/${worker.capacity ?? '?'}`);
|
|
138
|
+
if (worker.lastHeartbeat) {
|
|
139
|
+
const ago = Math.round((Date.now() - worker.lastHeartbeat) / 1000);
|
|
140
|
+
console.log(` Last heartbeat: ${ago}s ago`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
await disconnectRedis();
|
|
146
|
+
}
|
|
147
|
+
async function clearClaims() {
|
|
148
|
+
ensureRedis();
|
|
149
|
+
console.log('Clearing work claims...');
|
|
150
|
+
const claimKeys = await redisKeys(`${WORK_CLAIM_PREFIX}*`);
|
|
151
|
+
console.log(`Found ${claimKeys.length} claim(s)`);
|
|
152
|
+
if (claimKeys.length === 0) {
|
|
153
|
+
console.log('No claims to clear');
|
|
154
|
+
await disconnectRedis();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
let deleted = 0;
|
|
158
|
+
for (const key of claimKeys) {
|
|
159
|
+
const result = await redisDel(key);
|
|
160
|
+
if (result > 0) {
|
|
161
|
+
console.log(` Deleted: ${key}`);
|
|
162
|
+
deleted++;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
console.log(`\nCleared ${deleted} claim(s)`);
|
|
166
|
+
await disconnectRedis();
|
|
167
|
+
}
|
|
168
|
+
async function clearQueue() {
|
|
169
|
+
ensureRedis();
|
|
170
|
+
console.log('Clearing work queue...');
|
|
171
|
+
const queuedSessionIds = await redisZRangeByScore(WORK_QUEUE_KEY, '-inf', '+inf');
|
|
172
|
+
console.log(`Found ${queuedSessionIds.length} item(s) in queue sorted set`);
|
|
173
|
+
const workItems = await redisHGetAll(WORK_ITEMS_KEY);
|
|
174
|
+
const workItemCount = Object.keys(workItems).length;
|
|
175
|
+
console.log(`Found ${workItemCount} item(s) in work items hash`);
|
|
176
|
+
// Show what we're clearing
|
|
177
|
+
for (const [sessionId, itemJson] of Object.entries(workItems)) {
|
|
178
|
+
try {
|
|
179
|
+
const work = JSON.parse(itemJson);
|
|
180
|
+
console.log(` - ${work.issueIdentifier ?? sessionId.slice(0, 8)} (workType: ${work.workType || 'development'})`);
|
|
181
|
+
if (work.claudeSessionId) {
|
|
182
|
+
console.log(` ${C.yellow}Has claudeSessionId: ${work.claudeSessionId.substring(0, 12)}${C.reset}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
console.log(` - [invalid JSON]: ${sessionId}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
let cleared = 0;
|
|
190
|
+
if (queuedSessionIds.length > 0) {
|
|
191
|
+
await redisDel(WORK_QUEUE_KEY);
|
|
192
|
+
cleared++;
|
|
193
|
+
}
|
|
194
|
+
if (workItemCount > 0) {
|
|
195
|
+
await redisDel(WORK_ITEMS_KEY);
|
|
196
|
+
cleared++;
|
|
197
|
+
}
|
|
198
|
+
const totalItems = Math.max(queuedSessionIds.length, workItemCount);
|
|
199
|
+
if (cleared > 0) {
|
|
200
|
+
console.log(`\nCleared ${totalItems} item(s) from work queue`);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
console.log('\nQueue was already empty');
|
|
204
|
+
}
|
|
205
|
+
await disconnectRedis();
|
|
206
|
+
}
|
|
207
|
+
async function clearAll() {
|
|
208
|
+
ensureRedis();
|
|
209
|
+
console.log('Clearing ALL state...\n');
|
|
210
|
+
// Clear work queue
|
|
211
|
+
const queuedSessionIds = await redisZRangeByScore(WORK_QUEUE_KEY, '-inf', '+inf');
|
|
212
|
+
const workItems = await redisHGetAll(WORK_ITEMS_KEY);
|
|
213
|
+
if (queuedSessionIds.length > 0)
|
|
214
|
+
await redisDel(WORK_QUEUE_KEY);
|
|
215
|
+
if (Object.keys(workItems).length > 0)
|
|
216
|
+
await redisDel(WORK_ITEMS_KEY);
|
|
217
|
+
console.log(`Cleared ${Math.max(queuedSessionIds.length, Object.keys(workItems).length)} queue items`);
|
|
218
|
+
// Clear all sessions
|
|
219
|
+
const sessionKeys = await redisKeys(`${SESSION_KEY_PREFIX}*`);
|
|
220
|
+
for (const key of sessionKeys) {
|
|
221
|
+
await redisDel(key);
|
|
222
|
+
}
|
|
223
|
+
console.log(`Cleared ${sessionKeys.length} sessions`);
|
|
224
|
+
// Clear all claims
|
|
225
|
+
const claimKeys = await redisKeys(`${WORK_CLAIM_PREFIX}*`);
|
|
226
|
+
for (const key of claimKeys) {
|
|
227
|
+
await redisDel(key);
|
|
228
|
+
}
|
|
229
|
+
console.log(`Cleared ${claimKeys.length} claims`);
|
|
230
|
+
// Clear all workers
|
|
231
|
+
const workerKeys = await redisKeys(`${WORKER_PREFIX}*`);
|
|
232
|
+
for (const key of workerKeys) {
|
|
233
|
+
await redisDel(key);
|
|
234
|
+
}
|
|
235
|
+
console.log(`Cleared ${workerKeys.length} worker registrations`);
|
|
236
|
+
console.log('\nAll cleared!');
|
|
237
|
+
await disconnectRedis();
|
|
238
|
+
}
|
|
239
|
+
async function resetWorkState() {
|
|
240
|
+
ensureRedis();
|
|
241
|
+
console.log('Resetting work state...');
|
|
242
|
+
console.log('-'.repeat(60));
|
|
243
|
+
let totalCleared = 0;
|
|
244
|
+
// 1. Clear work claims
|
|
245
|
+
console.log('\nClearing work claims...');
|
|
246
|
+
const claimKeys = await redisKeys(`${WORK_CLAIM_PREFIX}*`);
|
|
247
|
+
console.log(` Found ${claimKeys.length} claim(s)`);
|
|
248
|
+
for (const key of claimKeys) {
|
|
249
|
+
const result = await redisDel(key);
|
|
250
|
+
if (result > 0) {
|
|
251
|
+
console.log(` Deleted: ${key}`);
|
|
252
|
+
totalCleared++;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// 2. Clear work queue
|
|
256
|
+
console.log('\nClearing work queue...');
|
|
257
|
+
const queuedSessionIds = await redisZRangeByScore(WORK_QUEUE_KEY, '-inf', '+inf');
|
|
258
|
+
console.log(` Found ${queuedSessionIds.length} queued item(s) in sorted set`);
|
|
259
|
+
const workItems = await redisHGetAll(WORK_ITEMS_KEY);
|
|
260
|
+
const workItemCount = Object.keys(workItems).length;
|
|
261
|
+
console.log(` Found ${workItemCount} item(s) in work items hash`);
|
|
262
|
+
for (const [sessionId, itemJson] of Object.entries(workItems)) {
|
|
263
|
+
try {
|
|
264
|
+
const work = JSON.parse(itemJson);
|
|
265
|
+
console.log(` - ${work.issueIdentifier ?? sessionId.slice(0, 8)} (workType: ${work.workType || 'development'})`);
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
console.log(` - [invalid item: ${sessionId}]`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (queuedSessionIds.length > 0 || workItemCount > 0) {
|
|
272
|
+
await redisDel(WORK_QUEUE_KEY);
|
|
273
|
+
await redisDel(WORK_ITEMS_KEY);
|
|
274
|
+
totalCleared += Math.max(queuedSessionIds.length, workItemCount);
|
|
275
|
+
console.log(` Cleared queue and items hash`);
|
|
276
|
+
}
|
|
277
|
+
// 3. Reset stuck sessions
|
|
278
|
+
console.log('\nResetting stuck sessions...');
|
|
279
|
+
const sessionKeys = await redisKeys(`${SESSION_KEY_PREFIX}*`);
|
|
280
|
+
console.log(` Found ${sessionKeys.length} session(s)`);
|
|
281
|
+
let sessionsReset = 0;
|
|
282
|
+
for (const key of sessionKeys) {
|
|
283
|
+
const session = await redisGet(key);
|
|
284
|
+
if (!session)
|
|
285
|
+
continue;
|
|
286
|
+
if (session.status === 'running' || session.status === 'claimed') {
|
|
287
|
+
console.log(` Resetting: ${session.issueIdentifier || session.linearSessionId}`);
|
|
288
|
+
console.log(` Status: ${session.status}, WorkerId: ${session.workerId || 'none'}`);
|
|
289
|
+
const updated = {
|
|
290
|
+
...session,
|
|
291
|
+
status: 'pending',
|
|
292
|
+
workerId: undefined,
|
|
293
|
+
claimedAt: undefined,
|
|
294
|
+
claudeSessionId: undefined,
|
|
295
|
+
updatedAt: Math.floor(Date.now() / 1000),
|
|
296
|
+
};
|
|
297
|
+
await redisSet(key, updated, 24 * 60 * 60);
|
|
298
|
+
sessionsReset++;
|
|
299
|
+
console.log(` Reset to pending`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
console.log('\n' + '-'.repeat(60));
|
|
303
|
+
console.log(`\nReset complete:`);
|
|
304
|
+
console.log(` - Claims cleared: ${claimKeys.length}`);
|
|
305
|
+
console.log(` - Queue items cleared: ${Math.max(queuedSessionIds.length, workItemCount)}`);
|
|
306
|
+
console.log(` - Sessions reset: ${sessionsReset}`);
|
|
307
|
+
await disconnectRedis();
|
|
308
|
+
}
|
|
309
|
+
async function removeSession(sessionId) {
|
|
310
|
+
ensureRedis();
|
|
311
|
+
let found = false;
|
|
312
|
+
// Find session by partial ID match
|
|
313
|
+
const keys = await redisKeys(`${SESSION_KEY_PREFIX}*`);
|
|
314
|
+
for (const key of keys) {
|
|
315
|
+
if (key.includes(sessionId)) {
|
|
316
|
+
await redisDel(key);
|
|
317
|
+
console.log(`Removed session: ${key.replace(SESSION_KEY_PREFIX, '')}`);
|
|
318
|
+
found = true;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// Also remove from queue if present
|
|
322
|
+
const workItems = await redisHGetAll(WORK_ITEMS_KEY);
|
|
323
|
+
for (const [sid, itemJson] of Object.entries(workItems)) {
|
|
324
|
+
if (sid.includes(sessionId)) {
|
|
325
|
+
// Remove from hash via direct Redis command
|
|
326
|
+
const redis = getRedisClient();
|
|
327
|
+
await redis.hdel(WORK_ITEMS_KEY, sid);
|
|
328
|
+
// Remove from sorted set
|
|
329
|
+
const { redisZRem } = await import('@supaku/agentfactory-server');
|
|
330
|
+
await redisZRem(WORK_QUEUE_KEY, sid);
|
|
331
|
+
const work = JSON.parse(itemJson);
|
|
332
|
+
console.log(`Removed from queue: ${work.issueIdentifier ?? sid.slice(0, 8)}`);
|
|
333
|
+
found = true;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// Remove claim if present
|
|
337
|
+
const claimKeys = await redisKeys(`${WORK_CLAIM_PREFIX}*`);
|
|
338
|
+
for (const key of claimKeys) {
|
|
339
|
+
if (key.includes(sessionId)) {
|
|
340
|
+
await redisDel(key);
|
|
341
|
+
console.log(`Removed claim: ${key}`);
|
|
342
|
+
found = true;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (!found) {
|
|
346
|
+
console.log(`No session found matching: ${sessionId}`);
|
|
347
|
+
}
|
|
348
|
+
await disconnectRedis();
|
|
349
|
+
}
|
|
350
|
+
function printUsage() {
|
|
351
|
+
console.log(`
|
|
352
|
+
${C.cyan}AgentFactory Queue Admin${C.reset} - Manage Redis work queue and sessions
|
|
353
|
+
|
|
354
|
+
${C.yellow}Usage:${C.reset}
|
|
355
|
+
af-queue-admin <command>
|
|
356
|
+
|
|
357
|
+
${C.yellow}Commands:${C.reset}
|
|
358
|
+
list List all queued work items
|
|
359
|
+
sessions List all sessions
|
|
360
|
+
workers List all registered workers
|
|
361
|
+
clear-claims Clear stale work claims
|
|
362
|
+
clear-queue Clear the work queue
|
|
363
|
+
clear-all Clear queue, sessions, claims, and workers
|
|
364
|
+
reset Full state reset (claims + queue + stuck sessions)
|
|
365
|
+
remove <id> Remove a specific session by ID (partial match)
|
|
366
|
+
|
|
367
|
+
${C.yellow}Examples:${C.reset}
|
|
368
|
+
af-queue-admin list
|
|
369
|
+
af-queue-admin sessions
|
|
370
|
+
af-queue-admin clear-queue
|
|
371
|
+
af-queue-admin reset
|
|
372
|
+
af-queue-admin remove abc123
|
|
373
|
+
`);
|
|
374
|
+
}
|
|
375
|
+
async function main() {
|
|
376
|
+
const command = process.argv[2];
|
|
377
|
+
const arg = process.argv[3];
|
|
378
|
+
switch (command) {
|
|
379
|
+
case 'list':
|
|
380
|
+
await listQueue();
|
|
381
|
+
break;
|
|
382
|
+
case 'sessions':
|
|
383
|
+
await listSessions();
|
|
384
|
+
break;
|
|
385
|
+
case 'workers':
|
|
386
|
+
await listWorkersFn();
|
|
387
|
+
break;
|
|
388
|
+
case 'clear-claims':
|
|
389
|
+
await clearClaims();
|
|
390
|
+
break;
|
|
391
|
+
case 'clear-queue':
|
|
392
|
+
await clearQueue();
|
|
393
|
+
break;
|
|
394
|
+
case 'clear-all':
|
|
395
|
+
await clearAll();
|
|
396
|
+
break;
|
|
397
|
+
case 'reset':
|
|
398
|
+
await resetWorkState();
|
|
399
|
+
break;
|
|
400
|
+
case 'remove':
|
|
401
|
+
if (!arg) {
|
|
402
|
+
console.error('Usage: af-queue-admin remove <session-id>');
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
await removeSession(arg);
|
|
406
|
+
break;
|
|
407
|
+
case '--help':
|
|
408
|
+
case '-h':
|
|
409
|
+
case 'help':
|
|
410
|
+
default:
|
|
411
|
+
printUsage();
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
main().catch((error) => {
|
|
416
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
417
|
+
process.exit(1);
|
|
418
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=worker-fleet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-fleet.d.ts","sourceRoot":"","sources":["../../src/worker-fleet.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;GAmBG"}
|