@supaku/agentfactory-cli 0.3.0 → 0.4.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.
Files changed (34) hide show
  1. package/dist/src/analyze-logs.d.ts +2 -2
  2. package/dist/src/analyze-logs.js +23 -194
  3. package/dist/src/cleanup.d.ts +2 -6
  4. package/dist/src/cleanup.d.ts.map +1 -1
  5. package/dist/src/cleanup.js +24 -225
  6. package/dist/src/lib/analyze-logs-runner.d.ts +47 -0
  7. package/dist/src/lib/analyze-logs-runner.d.ts.map +1 -0
  8. package/dist/src/lib/analyze-logs-runner.js +216 -0
  9. package/dist/src/lib/cleanup-runner.d.ts +28 -0
  10. package/dist/src/lib/cleanup-runner.d.ts.map +1 -0
  11. package/dist/src/lib/cleanup-runner.js +224 -0
  12. package/dist/src/lib/orchestrator-runner.d.ts +45 -0
  13. package/dist/src/lib/orchestrator-runner.d.ts.map +1 -0
  14. package/dist/src/lib/orchestrator-runner.js +144 -0
  15. package/dist/src/lib/queue-admin-runner.d.ts +30 -0
  16. package/dist/src/lib/queue-admin-runner.d.ts.map +1 -0
  17. package/dist/src/lib/queue-admin-runner.js +378 -0
  18. package/dist/src/lib/worker-fleet-runner.d.ts +28 -0
  19. package/dist/src/lib/worker-fleet-runner.d.ts.map +1 -0
  20. package/dist/src/lib/worker-fleet-runner.js +224 -0
  21. package/dist/src/lib/worker-runner.d.ts +31 -0
  22. package/dist/src/lib/worker-runner.d.ts.map +1 -0
  23. package/dist/src/lib/worker-runner.js +735 -0
  24. package/dist/src/orchestrator.d.ts +1 -1
  25. package/dist/src/orchestrator.js +42 -106
  26. package/dist/src/queue-admin.d.ts +3 -2
  27. package/dist/src/queue-admin.d.ts.map +1 -1
  28. package/dist/src/queue-admin.js +38 -360
  29. package/dist/src/worker-fleet.d.ts +1 -1
  30. package/dist/src/worker-fleet.js +23 -162
  31. package/dist/src/worker.d.ts +1 -0
  32. package/dist/src/worker.d.ts.map +1 -1
  33. package/dist/src/worker.js +33 -702
  34. package/package.json +28 -4
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * AgentFactory Orchestrator CLI
4
4
  *
5
- * Local script to spawn concurrent coding agents on backlog issues.
5
+ * Thin wrapper around the orchestrator runner.
6
6
  *
7
7
  * Usage:
8
8
  * af-orchestrator [options]
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * AgentFactory Orchestrator CLI
4
4
  *
5
- * Local script to spawn concurrent coding agents on backlog issues.
5
+ * Thin wrapper around the orchestrator runner.
6
6
  *
7
7
  * Usage:
8
8
  * af-orchestrator [options]
@@ -18,22 +18,10 @@
18
18
  * LINEAR_API_KEY Required API key for Linear authentication
19
19
  */
20
20
  import path from 'path';
21
- import { execSync } from 'child_process';
22
21
  import { config } from 'dotenv';
23
22
  // Load environment variables from .env.local
24
23
  config({ path: path.resolve(process.cwd(), '.env.local') });
25
- import { createOrchestrator } from '@supaku/agentfactory';
26
- function getGitRoot() {
27
- try {
28
- return execSync('git rev-parse --show-toplevel', {
29
- encoding: 'utf-8',
30
- stdio: ['pipe', 'pipe', 'pipe'],
31
- }).trim();
32
- }
33
- catch {
34
- return process.cwd();
35
- }
36
- }
24
+ import { runOrchestrator } from './lib/orchestrator-runner.js';
37
25
  function parseArgs() {
38
26
  const args = process.argv.slice(2);
39
27
  const result = {
@@ -98,18 +86,6 @@ Examples:
98
86
  af-orchestrator --project MyProject --dry-run
99
87
  `);
100
88
  }
101
- function formatDuration(ms) {
102
- const seconds = Math.floor(ms / 1000);
103
- const minutes = Math.floor(seconds / 60);
104
- const hours = Math.floor(minutes / 60);
105
- if (hours > 0) {
106
- return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
107
- }
108
- if (minutes > 0) {
109
- return `${minutes}m ${seconds % 60}s`;
110
- }
111
- return `${seconds}s`;
112
- }
113
89
  async function main() {
114
90
  const args = parseArgs();
115
91
  if (!process.env.LINEAR_API_KEY) {
@@ -122,95 +98,43 @@ async function main() {
122
98
  console.log(`Max concurrent: ${args.max}`);
123
99
  console.log(`Dry run: ${args.dryRun}`);
124
100
  console.log('');
125
- const gitRoot = getGitRoot();
126
- const orchestrator = createOrchestrator({
127
- project: args.project,
128
- maxConcurrent: args.max,
129
- worktreePath: path.resolve(gitRoot, '.worktrees'),
130
- }, {
131
- onIssueSelected: (issue) => {
132
- console.log(`Selected: ${issue.identifier} - ${issue.title}`);
133
- console.log(` URL: ${issue.url}`);
134
- console.log(` Labels: ${issue.labels.join(', ') || 'none'}`);
135
- },
136
- onAgentStart: (agent) => {
137
- console.log(`Agent started: ${agent.identifier} (PID: ${agent.pid})`);
138
- console.log(` Worktree: ${agent.worktreePath}`);
139
- },
140
- onAgentComplete: (agent) => {
141
- const duration = agent.completedAt
142
- ? formatDuration(agent.completedAt.getTime() - agent.startedAt.getTime())
143
- : 'unknown';
144
- console.log(`Agent completed: ${agent.identifier} (${duration})`);
145
- },
146
- onAgentError: (agent, error) => {
147
- console.error(`Agent failed: ${agent.identifier}`);
148
- console.error(` Error: ${error.message}`);
149
- },
150
- onAgentIncomplete: (agent) => {
151
- const duration = agent.completedAt
152
- ? formatDuration(agent.completedAt.getTime() - agent.startedAt.getTime())
153
- : 'unknown';
154
- console.warn(`Agent incomplete: ${agent.identifier} (${duration})`);
155
- console.warn(` Reason: ${agent.incompleteReason ?? 'unknown'}`);
156
- console.warn(` Worktree preserved: ${agent.worktreePath}`);
157
- },
158
- });
159
- try {
160
- if (args.single) {
161
- console.log(`Processing single issue: ${args.single}`);
162
- if (args.dryRun) {
163
- console.log('[DRY RUN] Would spawn agent for:', args.single);
164
- return;
165
- }
166
- await orchestrator.spawnAgentForIssue(args.single);
167
- console.log(`Agent spawned for ${args.single}`);
168
- if (args.wait) {
169
- console.log('Waiting for agent to complete...');
170
- await orchestrator.waitForAll();
171
- }
172
- return;
173
- }
101
+ if (args.single) {
102
+ console.log(`Processing single issue: ${args.single}`);
174
103
  if (args.dryRun) {
175
- const issues = await orchestrator.getBacklogIssues();
176
- if (issues.length === 0) {
177
- console.log('No backlog issues found');
178
- return;
179
- }
180
- console.log(`[DRY RUN] Would process ${issues.length} issue(s):`);
181
- for (const issue of issues) {
182
- console.log(` - ${issue.identifier}: ${issue.title}`);
183
- console.log(` Priority: ${issue.priority || 'none'}`);
184
- console.log(` Labels: ${issue.labels.join(', ') || 'none'}`);
185
- }
104
+ console.log('[DRY RUN] Would spawn agent for:', args.single);
186
105
  return;
187
106
  }
188
- const result = await orchestrator.run();
189
- console.log('');
190
- console.log('Orchestrator started');
191
- console.log(` Agents spawned: ${result.agents.length}`);
192
- console.log(` Errors: ${result.errors.length}`);
193
- if (result.errors.length > 0) {
107
+ }
108
+ try {
109
+ const result = await runOrchestrator({
110
+ linearApiKey: process.env.LINEAR_API_KEY,
111
+ project: args.project,
112
+ max: args.max,
113
+ single: args.single,
114
+ wait: args.wait,
115
+ dryRun: args.dryRun,
116
+ });
117
+ if (!args.single && !args.dryRun) {
194
118
  console.log('');
195
- console.log('Errors:');
196
- for (const { issueId, error } of result.errors) {
197
- console.log(` ${issueId}: ${error.message}`);
119
+ console.log('Orchestrator started');
120
+ console.log(` Agents spawned: ${result.agentsSpawned}`);
121
+ console.log(` Errors: ${result.errors.length}`);
122
+ if (result.errors.length > 0) {
123
+ console.log('');
124
+ console.log('Errors:');
125
+ for (const { issueId, error } of result.errors) {
126
+ console.log(` ${issueId}: ${error.message}`);
127
+ }
198
128
  }
199
129
  }
200
- if (args.wait && result.agents.length > 0) {
201
- console.log('');
202
- console.log('Waiting for all agents to complete...');
203
- process.on('SIGINT', () => {
204
- console.log('');
205
- console.log('Received SIGINT, stopping agents...');
206
- orchestrator.stopAll();
207
- process.exit(1);
208
- });
209
- const completedAgents = await orchestrator.waitForAll();
130
+ if (args.single) {
131
+ console.log(`Agent spawned for ${args.single}`);
132
+ }
133
+ if (args.wait && result.completed.length > 0) {
210
134
  console.log('');
211
135
  console.log('All agents completed');
212
136
  console.log('Results:');
213
- for (const agent of completedAgents) {
137
+ for (const agent of result.completed) {
214
138
  const duration = agent.completedAt
215
139
  ? formatDuration(agent.completedAt.getTime() - agent.startedAt.getTime())
216
140
  : 'unknown';
@@ -224,6 +148,18 @@ async function main() {
224
148
  process.exit(1);
225
149
  }
226
150
  }
151
+ function formatDuration(ms) {
152
+ const seconds = Math.floor(ms / 1000);
153
+ const minutes = Math.floor(seconds / 60);
154
+ const hours = Math.floor(minutes / 60);
155
+ if (hours > 0) {
156
+ return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
157
+ }
158
+ if (minutes > 0) {
159
+ return `${minutes}m ${seconds % 60}s`;
160
+ }
161
+ return `${seconds}s`;
162
+ }
227
163
  main().catch((error) => {
228
164
  console.error('Fatal error:', error);
229
165
  process.exit(1);
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * AgentFactory Queue Admin
3
+ * AgentFactory Queue Admin CLI
4
4
  *
5
- * Manage the Redis work queue, sessions, claims, and workers.
5
+ * Thin wrapper around the queue-admin runner. Handles dotenv, arg parsing,
6
+ * and process.exit so the runner stays process-agnostic.
6
7
  *
7
8
  * Usage:
8
9
  * af-queue-admin <command>
@@ -1 +1 @@
1
- {"version":3,"file":"queue-admin.d.ts","sourceRoot":"","sources":["../../src/queue-admin.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;GAoBG"}
1
+ {"version":3,"file":"queue-admin.d.ts","sourceRoot":"","sources":["../../src/queue-admin.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;GAqBG"}
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * AgentFactory Queue Admin
3
+ * AgentFactory Queue Admin CLI
4
4
  *
5
- * Manage the Redis work queue, sessions, claims, and workers.
5
+ * Thin wrapper around the queue-admin runner. Handles dotenv, arg parsing,
6
+ * and process.exit so the runner stays process-agnostic.
6
7
  *
7
8
  * Usage:
8
9
  * af-queue-admin <command>
@@ -24,329 +25,10 @@ import path from 'path';
24
25
  import { config } from 'dotenv';
25
26
  // Load environment variables from .env.local in CWD
26
27
  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
- }
28
+ import { runQueueAdmin, C } from './lib/queue-admin-runner.js';
29
+ // ---------------------------------------------------------------------------
30
+ // Usage
31
+ // ---------------------------------------------------------------------------
350
32
  function printUsage() {
351
33
  console.log(`
352
34
  ${C.cyan}AgentFactory Queue Admin${C.reset} - Manage Redis work queue and sessions
@@ -372,45 +54,41 @@ ${C.yellow}Examples:${C.reset}
372
54
  af-queue-admin remove abc123
373
55
  `);
374
56
  }
57
+ // ---------------------------------------------------------------------------
58
+ // Valid commands
59
+ // ---------------------------------------------------------------------------
60
+ const VALID_COMMANDS = new Set([
61
+ 'list',
62
+ 'sessions',
63
+ 'workers',
64
+ 'clear-claims',
65
+ 'clear-queue',
66
+ 'clear-all',
67
+ 'reset',
68
+ 'remove',
69
+ ]);
70
+ // ---------------------------------------------------------------------------
71
+ // Main
72
+ // ---------------------------------------------------------------------------
375
73
  async function main() {
376
74
  const command = process.argv[2];
377
75
  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;
76
+ if (!command || command === 'help' || command === '--help' || command === '-h') {
77
+ printUsage();
78
+ return;
79
+ }
80
+ if (!VALID_COMMANDS.has(command)) {
81
+ printUsage();
82
+ return;
83
+ }
84
+ if (command === 'remove' && !arg) {
85
+ console.error('Usage: af-queue-admin remove <session-id>');
86
+ process.exit(1);
413
87
  }
88
+ await runQueueAdmin({
89
+ command: command,
90
+ sessionId: arg,
91
+ });
414
92
  }
415
93
  main().catch((error) => {
416
94
  console.error('Error:', error instanceof Error ? error.message : error);
@@ -3,7 +3,7 @@
3
3
  * AgentFactory Worker Fleet Manager
4
4
  *
5
5
  * Spawns and manages multiple worker processes for parallel agent execution.
6
- * Each worker runs as a separate process with its own resources.
6
+ * Thin wrapper around the programmatic runner in ./lib/worker-fleet-runner.js.
7
7
  *
8
8
  * Usage:
9
9
  * af-worker-fleet [options]