@swarmify/agents-mcp 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/agents.js ADDED
@@ -0,0 +1,663 @@
1
+ import { spawn, execSync } from 'child_process';
2
+ import * as fs from 'fs/promises';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
5
+ import { randomUUID } from 'crypto';
6
+ import { resolveAgentsDir } from './persistence.js';
7
+ import { normalizeEvents } from './parsers.js';
8
+ export var AgentStatus;
9
+ (function (AgentStatus) {
10
+ AgentStatus["RUNNING"] = "running";
11
+ AgentStatus["COMPLETED"] = "completed";
12
+ AgentStatus["FAILED"] = "failed";
13
+ AgentStatus["STOPPED"] = "stopped";
14
+ })(AgentStatus || (AgentStatus = {}));
15
+ // Base commands for plan mode (read-only, may prompt for confirmation)
16
+ export const AGENT_COMMANDS = {
17
+ codex: ['codex', 'exec', '--sandbox', 'workspace-write', '{prompt}', '--json'],
18
+ cursor: ['cursor-agent', '-p', '--output-format', 'stream-json', '{prompt}'],
19
+ gemini: ['gemini', '{prompt}', '--output-format', 'stream-json'],
20
+ claude: ['claude', '-p', '--verbose', '{prompt}', '--output-format', 'stream-json', '--permission-mode', 'plan'],
21
+ };
22
+ // Model mappings by effort level per agent type
23
+ // - medium: balanced models (default)
24
+ // - high: max-capability models
25
+ // Note: cursor always uses composer-1, claude always uses opus-4.5
26
+ export const EFFORT_MODEL_MAP = {
27
+ medium: {
28
+ codex: 'gpt-5.2-codex',
29
+ gemini: 'gemini-3-flash-preview',
30
+ claude: 'opus-4.5',
31
+ cursor: 'composer-1',
32
+ },
33
+ high: {
34
+ codex: 'gpt-5.1-codex-max',
35
+ gemini: 'gemini-3-pro-preview',
36
+ claude: 'opus-4.5',
37
+ cursor: 'composer-1',
38
+ },
39
+ };
40
+ // Suffix appended to all prompts to ensure agents provide a summary
41
+ const PROMPT_SUFFIX = `
42
+
43
+ When you're done, provide a brief summary of:
44
+ 1. What you did (1-2 sentences)
45
+ 2. Key files modified and why
46
+ 3. Any important classes, functions, or components you added/changed`;
47
+ // Prefix for Claude agents in plan mode - explains the headless plan mode restrictions
48
+ const CLAUDE_PLAN_MODE_PREFIX = `You are running in HEADLESS PLAN MODE. This mode works like normal plan mode with one exception: you cannot write to ~/.claude/plans/ directory. Instead of writing a plan file, output your complete plan/response as your final message.
49
+
50
+ `;
51
+ const VALID_MODES = ['plan', 'edit'];
52
+ function normalizeModeValue(modeValue) {
53
+ if (!modeValue)
54
+ return null;
55
+ const normalized = modeValue.trim().toLowerCase();
56
+ if (VALID_MODES.includes(normalized)) {
57
+ return normalized;
58
+ }
59
+ return null;
60
+ }
61
+ function defaultModeFromEnv() {
62
+ for (const envVar of ['AGENT_SWARM_MODE', 'AGENT_SWARM_DEFAULT_MODE']) {
63
+ const rawValue = process.env[envVar];
64
+ const parsed = normalizeModeValue(rawValue);
65
+ if (parsed) {
66
+ return parsed;
67
+ }
68
+ if (rawValue) {
69
+ console.warn(`Invalid ${envVar}='${rawValue}'. Use 'plan' or 'edit'. Falling back to plan mode.`);
70
+ }
71
+ }
72
+ return 'plan';
73
+ }
74
+ export function resolveMode(requestedMode, defaultMode = 'plan') {
75
+ const normalizedDefault = normalizeModeValue(defaultMode);
76
+ if (!normalizedDefault) {
77
+ throw new Error(`Invalid default mode '${defaultMode}'. Use 'plan' or 'edit'.`);
78
+ }
79
+ if (requestedMode !== null && requestedMode !== undefined) {
80
+ const normalizedMode = normalizeModeValue(requestedMode);
81
+ if (!normalizedMode) {
82
+ throw new Error(`Invalid mode '${requestedMode}'. Valid modes: 'plan' (read-only) or 'edit' (can write).`);
83
+ }
84
+ return normalizedMode;
85
+ }
86
+ return normalizedDefault;
87
+ }
88
+ export function checkCliAvailable(agentType) {
89
+ const cmdTemplate = AGENT_COMMANDS[agentType];
90
+ if (!cmdTemplate) {
91
+ return [false, `Unknown agent type: ${agentType}`];
92
+ }
93
+ const executable = cmdTemplate[0];
94
+ try {
95
+ const whichPath = execSync(`which ${executable}`, { encoding: 'utf-8' }).trim();
96
+ return [true, whichPath];
97
+ }
98
+ catch {
99
+ return [false, `CLI tool '${executable}' not found in PATH. Install it first.`];
100
+ }
101
+ }
102
+ export function checkAllClis() {
103
+ const results = {};
104
+ for (const agentType of Object.keys(AGENT_COMMANDS)) {
105
+ const [available, pathOrError] = checkCliAvailable(agentType);
106
+ if (available) {
107
+ results[agentType] = { installed: true, path: pathOrError, error: null };
108
+ }
109
+ else {
110
+ results[agentType] = { installed: false, path: null, error: pathOrError };
111
+ }
112
+ }
113
+ return results;
114
+ }
115
+ let AGENTS_DIR = null;
116
+ export async function getAgentsDir() {
117
+ if (!AGENTS_DIR) {
118
+ AGENTS_DIR = await resolveAgentsDir();
119
+ }
120
+ return AGENTS_DIR;
121
+ }
122
+ export class AgentProcess {
123
+ agentId;
124
+ taskName;
125
+ agentType;
126
+ prompt;
127
+ cwd;
128
+ mode = 'plan';
129
+ pid = null;
130
+ status = AgentStatus.RUNNING;
131
+ startedAt = new Date();
132
+ completedAt = null;
133
+ eventsCache = [];
134
+ lastReadPos = 0;
135
+ baseDir = null;
136
+ constructor(agentId, taskName, agentType, prompt, cwd = null, mode = 'plan', pid = null, status = AgentStatus.RUNNING, startedAt = new Date(), completedAt = null, baseDir = null) {
137
+ this.agentId = agentId;
138
+ this.taskName = taskName;
139
+ this.agentType = agentType;
140
+ this.prompt = prompt;
141
+ this.cwd = cwd;
142
+ this.mode = mode;
143
+ this.pid = pid;
144
+ this.status = status;
145
+ this.startedAt = startedAt;
146
+ this.completedAt = completedAt;
147
+ this.baseDir = baseDir;
148
+ }
149
+ get isEditMode() {
150
+ return this.mode === 'edit';
151
+ }
152
+ async getAgentDir() {
153
+ const base = this.baseDir || await getAgentsDir();
154
+ return path.join(base, this.agentId);
155
+ }
156
+ async getStdoutPath() {
157
+ return path.join(await this.getAgentDir(), 'stdout.log');
158
+ }
159
+ async getMetaPath() {
160
+ return path.join(await this.getAgentDir(), 'meta.json');
161
+ }
162
+ toDict() {
163
+ return {
164
+ agent_id: this.agentId,
165
+ task_name: this.taskName,
166
+ agent_type: this.agentType,
167
+ status: this.status,
168
+ started_at: this.startedAt.toISOString(),
169
+ completed_at: this.completedAt?.toISOString() || null,
170
+ event_count: this.events.length,
171
+ duration: this.duration(),
172
+ mode: this.mode,
173
+ };
174
+ }
175
+ duration() {
176
+ let seconds;
177
+ if (this.completedAt) {
178
+ seconds = (this.completedAt.getTime() - this.startedAt.getTime()) / 1000;
179
+ }
180
+ else if (this.status === AgentStatus.RUNNING) {
181
+ seconds = (Date.now() - this.startedAt.getTime()) / 1000;
182
+ }
183
+ else {
184
+ return null;
185
+ }
186
+ if (seconds < 60) {
187
+ return `${Math.floor(seconds)} seconds`;
188
+ }
189
+ else {
190
+ const minutes = seconds / 60;
191
+ return `${minutes.toFixed(1)} minutes`;
192
+ }
193
+ }
194
+ get events() {
195
+ return this.eventsCache;
196
+ }
197
+ /**
198
+ * Return the latest timestamp we have seen in the agent's events.
199
+ * Falls back to null when none are available.
200
+ */
201
+ getLatestEventTime() {
202
+ let latest = null;
203
+ for (const event of this.eventsCache) {
204
+ const ts = event?.timestamp;
205
+ if (!ts)
206
+ continue;
207
+ const parsed = new Date(ts);
208
+ if (!Number.isNaN(parsed.getTime())) {
209
+ if (!latest || parsed > latest) {
210
+ latest = parsed;
211
+ }
212
+ }
213
+ }
214
+ return latest;
215
+ }
216
+ async readNewEvents() {
217
+ const stdoutPath = await this.getStdoutPath();
218
+ try {
219
+ const stats = await fs.stat(stdoutPath).catch(() => null);
220
+ if (!stats)
221
+ return;
222
+ const fd = await fs.open(stdoutPath, 'r');
223
+ const buffer = Buffer.alloc(1024 * 1024);
224
+ const { bytesRead } = await fd.read(buffer, 0, buffer.length, this.lastReadPos);
225
+ await fd.close();
226
+ if (bytesRead === 0)
227
+ return;
228
+ const newContent = buffer.toString('utf-8', 0, bytesRead);
229
+ this.lastReadPos += bytesRead;
230
+ const lines = newContent.split('\n').map(l => l.trim()).filter(l => l);
231
+ for (const line of lines) {
232
+ try {
233
+ const rawEvent = JSON.parse(line);
234
+ const events = normalizeEvents(this.agentType, rawEvent);
235
+ for (const event of events) {
236
+ this.eventsCache.push(event);
237
+ if (event.type === 'result' || event.type === 'turn.completed' || event.type === 'thread.completed') {
238
+ if (event.status === 'success' || event.type === 'turn.completed') {
239
+ this.status = AgentStatus.COMPLETED;
240
+ this.completedAt = event.timestamp ? new Date(event.timestamp) : new Date();
241
+ }
242
+ else if (event.status === 'error') {
243
+ this.status = AgentStatus.FAILED;
244
+ this.completedAt = event.timestamp ? new Date(event.timestamp) : new Date();
245
+ }
246
+ }
247
+ }
248
+ }
249
+ catch {
250
+ this.eventsCache.push({
251
+ type: 'raw',
252
+ content: line,
253
+ timestamp: new Date().toISOString(),
254
+ });
255
+ }
256
+ }
257
+ }
258
+ catch (err) {
259
+ console.error(`Error reading events for agent ${this.agentId}:`, err);
260
+ }
261
+ }
262
+ async saveMeta() {
263
+ const agentDir = await this.getAgentDir();
264
+ await fs.mkdir(agentDir, { recursive: true });
265
+ const meta = {
266
+ agent_id: this.agentId,
267
+ task_name: this.taskName,
268
+ agent_type: this.agentType,
269
+ prompt: this.prompt,
270
+ cwd: this.cwd,
271
+ mode: this.mode,
272
+ pid: this.pid,
273
+ status: this.status,
274
+ started_at: this.startedAt.toISOString(),
275
+ completed_at: this.completedAt?.toISOString() || null,
276
+ };
277
+ const metaPath = await this.getMetaPath();
278
+ await fs.writeFile(metaPath, JSON.stringify(meta, null, 2));
279
+ }
280
+ static async loadFromDisk(agentId, baseDir = null) {
281
+ const base = baseDir || await getAgentsDir();
282
+ const agentDir = path.join(base, agentId);
283
+ const metaPath = path.join(agentDir, 'meta.json');
284
+ try {
285
+ await fs.access(metaPath);
286
+ }
287
+ catch {
288
+ return null;
289
+ }
290
+ try {
291
+ const metaContent = await fs.readFile(metaPath, 'utf-8');
292
+ const meta = JSON.parse(metaContent);
293
+ const agent = new AgentProcess(meta.agent_id, meta.task_name || 'default', meta.agent_type, meta.prompt, meta.cwd || null, meta.mode === 'edit' ? 'edit' : 'plan', meta.pid || null, AgentStatus[meta.status] || AgentStatus.RUNNING, new Date(meta.started_at), meta.completed_at ? new Date(meta.completed_at) : null, baseDir);
294
+ return agent;
295
+ }
296
+ catch {
297
+ return null;
298
+ }
299
+ }
300
+ isProcessAlive() {
301
+ if (!this.pid)
302
+ return false;
303
+ try {
304
+ process.kill(this.pid, 0);
305
+ return true;
306
+ }
307
+ catch {
308
+ return false;
309
+ }
310
+ }
311
+ async updateStatusFromProcess() {
312
+ if (!this.pid)
313
+ return;
314
+ if (this.isProcessAlive()) {
315
+ await this.readNewEvents();
316
+ return;
317
+ }
318
+ const fallbackCompletion = this.getLatestEventTime() || this.startedAt || new Date();
319
+ if (this.status === AgentStatus.RUNNING) {
320
+ const exitCode = await this.reapProcess();
321
+ await this.readNewEvents();
322
+ if (this.status === AgentStatus.RUNNING) {
323
+ if (exitCode !== null && exitCode !== 0) {
324
+ this.status = AgentStatus.FAILED;
325
+ }
326
+ else {
327
+ this.status = AgentStatus.COMPLETED;
328
+ }
329
+ this.completedAt = fallbackCompletion;
330
+ }
331
+ }
332
+ else if (!this.completedAt) {
333
+ this.completedAt = fallbackCompletion;
334
+ }
335
+ await this.saveMeta();
336
+ }
337
+ async reapProcess() {
338
+ if (!this.pid)
339
+ return null;
340
+ try {
341
+ process.kill(this.pid, 0);
342
+ return null;
343
+ }
344
+ catch {
345
+ return 1;
346
+ }
347
+ }
348
+ }
349
+ export class AgentManager {
350
+ agents = new Map();
351
+ maxAgents;
352
+ maxConcurrent;
353
+ agentsDir = '';
354
+ filterByCwd;
355
+ cleanupAgeDays;
356
+ defaultMode;
357
+ constructorAgentsDir = null;
358
+ constructor(maxAgents = 50, maxConcurrent = 10, agentsDir = null, defaultMode = null, filterByCwd = null, cleanupAgeDays = 7) {
359
+ this.maxAgents = maxAgents;
360
+ this.maxConcurrent = maxConcurrent;
361
+ this.constructorAgentsDir = agentsDir;
362
+ this.filterByCwd = filterByCwd;
363
+ this.cleanupAgeDays = cleanupAgeDays;
364
+ const resolvedDefaultMode = defaultMode ? normalizeModeValue(defaultMode) : defaultModeFromEnv();
365
+ if (!resolvedDefaultMode) {
366
+ throw new Error(`Invalid default_mode '${defaultMode}'. Use 'plan' or 'edit'.`);
367
+ }
368
+ this.defaultMode = resolvedDefaultMode;
369
+ this.initialize();
370
+ }
371
+ async initialize() {
372
+ this.agentsDir = this.constructorAgentsDir || await getAgentsDir();
373
+ await fs.mkdir(this.agentsDir, { recursive: true });
374
+ await this.loadExistingAgents();
375
+ }
376
+ getDefaultMode() {
377
+ return this.defaultMode;
378
+ }
379
+ async loadExistingAgents() {
380
+ try {
381
+ await fs.access(this.agentsDir);
382
+ }
383
+ catch {
384
+ return;
385
+ }
386
+ const cutoffDate = new Date(Date.now() - this.cleanupAgeDays * 24 * 60 * 60 * 1000);
387
+ let loadedCount = 0;
388
+ let skippedCwd = 0;
389
+ let cleanedOld = 0;
390
+ const entries = await fs.readdir(this.agentsDir);
391
+ for (const entry of entries) {
392
+ const agentDir = path.join(this.agentsDir, entry);
393
+ const stat = await fs.stat(agentDir).catch(() => null);
394
+ if (!stat || !stat.isDirectory())
395
+ continue;
396
+ const agentId = entry;
397
+ const agent = await AgentProcess.loadFromDisk(agentId, this.agentsDir);
398
+ if (!agent)
399
+ continue;
400
+ if (agent.completedAt && agent.completedAt < cutoffDate) {
401
+ try {
402
+ await fs.rm(agentDir, { recursive: true });
403
+ cleanedOld++;
404
+ }
405
+ catch (err) {
406
+ console.warn(`Failed to cleanup old agent ${agentId}:`, err);
407
+ }
408
+ continue;
409
+ }
410
+ if (this.filterByCwd !== null) {
411
+ const agentCwd = agent.cwd;
412
+ if (agentCwd !== this.filterByCwd) {
413
+ skippedCwd++;
414
+ continue;
415
+ }
416
+ }
417
+ await agent.updateStatusFromProcess();
418
+ this.agents.set(agentId, agent);
419
+ loadedCount++;
420
+ }
421
+ if (cleanedOld > 0) {
422
+ console.error(`Cleaned up ${cleanedOld} old agents (older than ${this.cleanupAgeDays} days)`);
423
+ }
424
+ if (skippedCwd > 0) {
425
+ console.error(`Skipped ${skippedCwd} agents (different CWD)`);
426
+ }
427
+ console.error(`Loaded ${loadedCount} agents from disk`);
428
+ }
429
+ async spawn(taskName, agentType, prompt, cwd = null, mode = null, effort = 'medium') {
430
+ await this.initialize();
431
+ const resolvedMode = resolveMode(mode, this.defaultMode);
432
+ // Resolve model from effort level
433
+ const resolvedModel = EFFORT_MODEL_MAP[effort][agentType];
434
+ const running = await this.listRunning();
435
+ if (running.length >= this.maxConcurrent) {
436
+ throw new Error(`Maximum concurrent agents (${this.maxConcurrent}) reached. Wait for an agent to complete or stop one first.`);
437
+ }
438
+ const [available, pathOrError] = checkCliAvailable(agentType);
439
+ if (!available) {
440
+ throw new Error(pathOrError || 'CLI tool not available');
441
+ }
442
+ // Resolve and validate cwd
443
+ let resolvedCwd = null;
444
+ if (cwd !== null) {
445
+ resolvedCwd = path.resolve(cwd);
446
+ const stat = await fs.stat(resolvedCwd).catch(() => null);
447
+ if (!stat) {
448
+ throw new Error(`Working directory does not exist: ${cwd}`);
449
+ }
450
+ if (!stat.isDirectory()) {
451
+ throw new Error(`Working directory is not a directory: ${cwd}`);
452
+ }
453
+ }
454
+ const agentId = randomUUID().substring(0, 8);
455
+ const cmd = this.buildCommand(agentType, prompt, resolvedMode, resolvedModel, resolvedCwd);
456
+ const agent = new AgentProcess(agentId, taskName, agentType, prompt, resolvedCwd, resolvedMode, null, AgentStatus.RUNNING, new Date(), null, this.agentsDir);
457
+ const agentDir = await agent.getAgentDir();
458
+ try {
459
+ await fs.mkdir(agentDir, { recursive: true });
460
+ }
461
+ catch (err) {
462
+ this.agents.delete(agent.agentId);
463
+ throw new Error(`Failed to create agent directory: ${err.message}`);
464
+ }
465
+ console.log(`Spawning ${agentType} agent ${agentId} [${resolvedMode}]: ${cmd.slice(0, 3).join(' ')}...`);
466
+ try {
467
+ const stdoutPath = await agent.getStdoutPath();
468
+ const stdoutFile = await fs.open(stdoutPath, 'w');
469
+ const stdoutFd = stdoutFile.fd;
470
+ const childProcess = spawn(cmd[0], cmd.slice(1), {
471
+ stdio: ['ignore', stdoutFd, stdoutFd],
472
+ cwd: resolvedCwd || undefined,
473
+ detached: true,
474
+ });
475
+ childProcess.unref();
476
+ stdoutFile.close().catch(() => { });
477
+ agent.pid = childProcess.pid || null;
478
+ await agent.saveMeta();
479
+ }
480
+ catch (err) {
481
+ await this.cleanupPartialAgent(agent);
482
+ console.error(`Failed to spawn agent ${agentId}:`, err);
483
+ throw new Error(`Failed to spawn agent: ${err.message}`);
484
+ }
485
+ this.agents.set(agentId, agent);
486
+ await this.cleanupOldAgents();
487
+ console.log(`Spawned agent ${agentId} with PID ${agent.pid}`);
488
+ return agent;
489
+ }
490
+ buildCommand(agentType, prompt, mode, model, cwd = null) {
491
+ const cmdTemplate = AGENT_COMMANDS[agentType];
492
+ if (!cmdTemplate) {
493
+ throw new Error(`Unknown agent type: ${agentType}`);
494
+ }
495
+ const isEditMode = mode === 'edit';
496
+ // Build the full prompt with prefix (for plan mode) and suffix
497
+ let fullPrompt = prompt + PROMPT_SUFFIX;
498
+ // For Claude in plan mode, add prefix explaining headless plan mode restrictions
499
+ if (agentType === 'claude' && !isEditMode) {
500
+ fullPrompt = CLAUDE_PLAN_MODE_PREFIX + fullPrompt;
501
+ }
502
+ let cmd = cmdTemplate.map(part => part.replace('{prompt}', fullPrompt));
503
+ // For Claude agents, load user's settings.json to inherit permissions
504
+ // and grant access to the working directory
505
+ if (agentType === 'claude') {
506
+ const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
507
+ cmd.push('--settings', settingsPath);
508
+ if (cwd) {
509
+ cmd.push('--add-dir', cwd);
510
+ }
511
+ }
512
+ // Add model flag for each agent type
513
+ if (agentType === 'codex') {
514
+ const execIndex = cmd.indexOf('exec');
515
+ const sandboxIndex = cmd.indexOf('--sandbox');
516
+ const insertIndex = sandboxIndex !== -1 ? sandboxIndex : execIndex + 1;
517
+ cmd.splice(insertIndex, 0, '--model', model);
518
+ }
519
+ else if (agentType === 'cursor') {
520
+ cmd.push('--model', model);
521
+ }
522
+ else if (agentType === 'gemini' || agentType === 'claude') {
523
+ cmd.push('--model', model);
524
+ }
525
+ if (isEditMode) {
526
+ cmd = this.applyEditMode(agentType, cmd);
527
+ }
528
+ return cmd;
529
+ }
530
+ applyEditMode(agentType, cmd) {
531
+ const editCmd = [...cmd];
532
+ switch (agentType) {
533
+ case 'codex':
534
+ editCmd.push('--full-auto');
535
+ break;
536
+ case 'cursor':
537
+ editCmd.push('-f');
538
+ break;
539
+ case 'gemini':
540
+ // Gemini CLI uses --yolo flag for auto-approve
541
+ editCmd.push('--yolo');
542
+ break;
543
+ case 'claude':
544
+ const permModeIndex = editCmd.indexOf('--permission-mode');
545
+ if (permModeIndex !== -1 && permModeIndex + 1 < editCmd.length) {
546
+ editCmd[permModeIndex + 1] = 'acceptEdits';
547
+ }
548
+ break;
549
+ }
550
+ return editCmd;
551
+ }
552
+ async get(agentId) {
553
+ await this.initialize();
554
+ let agent = this.agents.get(agentId) || null;
555
+ if (agent) {
556
+ await agent.readNewEvents();
557
+ await agent.updateStatusFromProcess();
558
+ return agent;
559
+ }
560
+ agent = await AgentProcess.loadFromDisk(agentId, this.agentsDir);
561
+ if (agent) {
562
+ await agent.readNewEvents();
563
+ await agent.updateStatusFromProcess();
564
+ this.agents.set(agentId, agent);
565
+ return agent;
566
+ }
567
+ return null;
568
+ }
569
+ async listAll() {
570
+ const agents = Array.from(this.agents.values());
571
+ for (const agent of agents) {
572
+ await agent.readNewEvents();
573
+ await agent.updateStatusFromProcess();
574
+ }
575
+ return agents;
576
+ }
577
+ async listRunning() {
578
+ const all = await this.listAll();
579
+ return all.filter(a => a.status === AgentStatus.RUNNING);
580
+ }
581
+ async listCompleted() {
582
+ const all = await this.listAll();
583
+ return all.filter(a => a.status !== AgentStatus.RUNNING);
584
+ }
585
+ async listByTask(taskName) {
586
+ const all = await this.listAll();
587
+ return all.filter(a => a.taskName === taskName);
588
+ }
589
+ async stopByTask(taskName) {
590
+ const agents = await this.listByTask(taskName);
591
+ const stopped = [];
592
+ const alreadyStopped = [];
593
+ for (const agent of agents) {
594
+ if (agent.status === AgentStatus.RUNNING) {
595
+ const success = await this.stop(agent.agentId);
596
+ if (success) {
597
+ stopped.push(agent.agentId);
598
+ }
599
+ }
600
+ else {
601
+ alreadyStopped.push(agent.agentId);
602
+ }
603
+ }
604
+ return { stopped, alreadyStopped };
605
+ }
606
+ async stop(agentId) {
607
+ await this.initialize();
608
+ const agent = this.agents.get(agentId);
609
+ if (!agent) {
610
+ return false;
611
+ }
612
+ if (agent.pid && agent.status === AgentStatus.RUNNING) {
613
+ try {
614
+ process.kill(-agent.pid, 'SIGTERM');
615
+ console.log(`Sent SIGTERM to agent ${agentId} (PID ${agent.pid})`);
616
+ await new Promise(resolve => setTimeout(resolve, 2000));
617
+ if (agent.isProcessAlive()) {
618
+ process.kill(-agent.pid, 'SIGKILL');
619
+ console.log(`Sent SIGKILL to agent ${agentId}`);
620
+ }
621
+ }
622
+ catch {
623
+ }
624
+ agent.status = AgentStatus.STOPPED;
625
+ agent.completedAt = new Date();
626
+ await agent.saveMeta();
627
+ console.log(`Stopped agent ${agentId}`);
628
+ return true;
629
+ }
630
+ return false;
631
+ }
632
+ async cleanupPartialAgent(agent) {
633
+ this.agents.delete(agent.agentId);
634
+ try {
635
+ const agentDir = await agent.getAgentDir();
636
+ await fs.rm(agentDir, { recursive: true });
637
+ }
638
+ catch (err) {
639
+ console.warn(`Failed to clean up agent directory:`, err);
640
+ }
641
+ }
642
+ async cleanupOldAgents() {
643
+ const completed = await this.listCompleted();
644
+ if (completed.length > this.maxAgents) {
645
+ completed.sort((a, b) => {
646
+ const aTime = a.completedAt?.getTime() || 0;
647
+ const bTime = b.completedAt?.getTime() || 0;
648
+ return aTime - bTime;
649
+ });
650
+ for (const agent of completed.slice(0, completed.length - this.maxAgents)) {
651
+ this.agents.delete(agent.agentId);
652
+ try {
653
+ const agentDir = await agent.getAgentDir();
654
+ await fs.rm(agentDir, { recursive: true });
655
+ }
656
+ catch (err) {
657
+ console.warn(`Failed to cleanup old agent ${agent.agentId}:`, err);
658
+ }
659
+ }
660
+ }
661
+ }
662
+ }
663
+ //# sourceMappingURL=agents.js.map