@renseiai/agentfactory-cli 0.8.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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/dist/src/agent.d.ts +20 -0
  4. package/dist/src/agent.d.ts.map +1 -0
  5. package/dist/src/agent.js +109 -0
  6. package/dist/src/analyze-logs.d.ts +26 -0
  7. package/dist/src/analyze-logs.d.ts.map +1 -0
  8. package/dist/src/analyze-logs.js +152 -0
  9. package/dist/src/cleanup.d.ts +17 -0
  10. package/dist/src/cleanup.d.ts.map +1 -0
  11. package/dist/src/cleanup.js +111 -0
  12. package/dist/src/governor.d.ts +26 -0
  13. package/dist/src/governor.d.ts.map +1 -0
  14. package/dist/src/governor.js +305 -0
  15. package/dist/src/index.d.ts +10 -0
  16. package/dist/src/index.d.ts.map +1 -0
  17. package/dist/src/index.js +76 -0
  18. package/dist/src/lib/agent-runner.d.ts +28 -0
  19. package/dist/src/lib/agent-runner.d.ts.map +1 -0
  20. package/dist/src/lib/agent-runner.js +272 -0
  21. package/dist/src/lib/analyze-logs-runner.d.ts +47 -0
  22. package/dist/src/lib/analyze-logs-runner.d.ts.map +1 -0
  23. package/dist/src/lib/analyze-logs-runner.js +216 -0
  24. package/dist/src/lib/auto-updater.d.ts +40 -0
  25. package/dist/src/lib/auto-updater.d.ts.map +1 -0
  26. package/dist/src/lib/auto-updater.js +109 -0
  27. package/dist/src/lib/cleanup-runner.d.ts +29 -0
  28. package/dist/src/lib/cleanup-runner.d.ts.map +1 -0
  29. package/dist/src/lib/cleanup-runner.js +295 -0
  30. package/dist/src/lib/governor-dependencies.d.ts +23 -0
  31. package/dist/src/lib/governor-dependencies.d.ts.map +1 -0
  32. package/dist/src/lib/governor-dependencies.js +361 -0
  33. package/dist/src/lib/governor-logger.d.ts +30 -0
  34. package/dist/src/lib/governor-logger.d.ts.map +1 -0
  35. package/dist/src/lib/governor-logger.js +210 -0
  36. package/dist/src/lib/governor-runner.d.ts +103 -0
  37. package/dist/src/lib/governor-runner.d.ts.map +1 -0
  38. package/dist/src/lib/governor-runner.js +210 -0
  39. package/dist/src/lib/linear-runner.d.ts +8 -0
  40. package/dist/src/lib/linear-runner.d.ts.map +1 -0
  41. package/dist/src/lib/linear-runner.js +7 -0
  42. package/dist/src/lib/orchestrator-runner.d.ts +51 -0
  43. package/dist/src/lib/orchestrator-runner.d.ts.map +1 -0
  44. package/dist/src/lib/orchestrator-runner.js +151 -0
  45. package/dist/src/lib/queue-admin-runner.d.ts +30 -0
  46. package/dist/src/lib/queue-admin-runner.d.ts.map +1 -0
  47. package/dist/src/lib/queue-admin-runner.js +378 -0
  48. package/dist/src/lib/sync-routes-runner.d.ts +28 -0
  49. package/dist/src/lib/sync-routes-runner.d.ts.map +1 -0
  50. package/dist/src/lib/sync-routes-runner.js +110 -0
  51. package/dist/src/lib/version.d.ts +35 -0
  52. package/dist/src/lib/version.d.ts.map +1 -0
  53. package/dist/src/lib/version.js +168 -0
  54. package/dist/src/lib/worker-fleet-runner.d.ts +32 -0
  55. package/dist/src/lib/worker-fleet-runner.d.ts.map +1 -0
  56. package/dist/src/lib/worker-fleet-runner.js +256 -0
  57. package/dist/src/lib/worker-runner.d.ts +33 -0
  58. package/dist/src/lib/worker-runner.d.ts.map +1 -0
  59. package/dist/src/lib/worker-runner.js +781 -0
  60. package/dist/src/linear.d.ts +37 -0
  61. package/dist/src/linear.d.ts.map +1 -0
  62. package/dist/src/linear.js +118 -0
  63. package/dist/src/orchestrator.d.ts +21 -0
  64. package/dist/src/orchestrator.d.ts.map +1 -0
  65. package/dist/src/orchestrator.js +190 -0
  66. package/dist/src/queue-admin.d.ts +25 -0
  67. package/dist/src/queue-admin.d.ts.map +1 -0
  68. package/dist/src/queue-admin.js +96 -0
  69. package/dist/src/sync-routes.d.ts +17 -0
  70. package/dist/src/sync-routes.d.ts.map +1 -0
  71. package/dist/src/sync-routes.js +100 -0
  72. package/dist/src/worker-fleet.d.ts +25 -0
  73. package/dist/src/worker-fleet.d.ts.map +1 -0
  74. package/dist/src/worker-fleet.js +140 -0
  75. package/dist/src/worker.d.ts +26 -0
  76. package/dist/src/worker.d.ts.map +1 -0
  77. package/dist/src/worker.js +135 -0
  78. package/package.json +175 -0
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Shared version utilities for AgentFactory CLI commands.
3
+ *
4
+ * Provides current version detection and npm update checking with
5
+ * file-based caching to avoid excessive network requests.
6
+ */
7
+ import path from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
10
+ import os from 'os';
11
+ // ---------------------------------------------------------------------------
12
+ // Current version
13
+ // ---------------------------------------------------------------------------
14
+ const PACKAGE_NAME = '@renseiai/agentfactory-cli';
15
+ /**
16
+ * Read the current package version from the CLI's package.json.
17
+ */
18
+ export function getVersion() {
19
+ try {
20
+ let dir = path.dirname(fileURLToPath(import.meta.url));
21
+ // Walk up from the current file until we find the CLI package.json
22
+ for (let i = 0; i < 5; i++) {
23
+ const candidate = path.join(dir, 'package.json');
24
+ if (existsSync(candidate)) {
25
+ const pkg = JSON.parse(readFileSync(candidate, 'utf-8'));
26
+ if (pkg.name === PACKAGE_NAME) {
27
+ return pkg.version ?? 'unknown';
28
+ }
29
+ }
30
+ dir = path.dirname(dir);
31
+ }
32
+ return 'unknown';
33
+ }
34
+ catch {
35
+ return 'unknown';
36
+ }
37
+ }
38
+ // ---------------------------------------------------------------------------
39
+ // Cache management
40
+ // ---------------------------------------------------------------------------
41
+ const CACHE_DIR = path.join(os.tmpdir(), 'agentfactory');
42
+ const CACHE_FILE = path.join(CACHE_DIR, 'update-check.json');
43
+ const CHECK_INTERVAL_MS = 4 * 60 * 60 * 1000; // 4 hours
44
+ function readCache() {
45
+ try {
46
+ if (!existsSync(CACHE_FILE))
47
+ return null;
48
+ const data = JSON.parse(readFileSync(CACHE_FILE, 'utf-8'));
49
+ if (typeof data.latestVersion !== 'string' || typeof data.checkedAt !== 'number')
50
+ return null;
51
+ return data;
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ function writeCache(entry) {
58
+ try {
59
+ if (!existsSync(CACHE_DIR)) {
60
+ mkdirSync(CACHE_DIR, { recursive: true });
61
+ }
62
+ writeFileSync(CACHE_FILE, JSON.stringify(entry), 'utf-8');
63
+ }
64
+ catch {
65
+ // Non-critical — silently ignore cache write failures
66
+ }
67
+ }
68
+ // ---------------------------------------------------------------------------
69
+ // npm registry fetch
70
+ // ---------------------------------------------------------------------------
71
+ async function fetchLatestVersion() {
72
+ try {
73
+ const controller = new AbortController();
74
+ const timeout = setTimeout(() => controller.abort(), 5000);
75
+ const response = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, { signal: controller.signal });
76
+ clearTimeout(timeout);
77
+ if (!response.ok)
78
+ return null;
79
+ const data = (await response.json());
80
+ return data.version ?? null;
81
+ }
82
+ catch {
83
+ return null;
84
+ }
85
+ }
86
+ // ---------------------------------------------------------------------------
87
+ // Semver comparison (major.minor.patch only)
88
+ // ---------------------------------------------------------------------------
89
+ function parseVersion(v) {
90
+ const match = v.match(/^(\d+)\.(\d+)\.(\d+)/);
91
+ if (!match)
92
+ return null;
93
+ return [parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3], 10)];
94
+ }
95
+ function isNewer(latest, current) {
96
+ const l = parseVersion(latest);
97
+ const c = parseVersion(current);
98
+ if (!l || !c)
99
+ return false;
100
+ if (l[0] !== c[0])
101
+ return l[0] > c[0];
102
+ if (l[1] !== c[1])
103
+ return l[1] > c[1];
104
+ return l[2] > c[2];
105
+ }
106
+ // ---------------------------------------------------------------------------
107
+ // Public API
108
+ // ---------------------------------------------------------------------------
109
+ /**
110
+ * Check whether a newer version is available on npm.
111
+ *
112
+ * Uses a file-based cache (4-hour TTL) to avoid hitting the registry
113
+ * on every CLI invocation. Returns null if the check is skipped or fails.
114
+ *
115
+ * Disabled when:
116
+ * - `AF_NO_UPDATE_CHECK=1` env var is set
117
+ * - `--no-update-check` was passed
118
+ * - Current version is 'unknown'
119
+ */
120
+ export async function checkForUpdate(opts) {
121
+ if (opts?.noUpdateCheck)
122
+ return null;
123
+ if (process.env.AF_NO_UPDATE_CHECK === '1' || process.env.AF_NO_UPDATE_CHECK === 'true')
124
+ return null;
125
+ const currentVersion = getVersion();
126
+ if (currentVersion === 'unknown')
127
+ return null;
128
+ // Check cache first
129
+ const cached = readCache();
130
+ if (cached && Date.now() - cached.checkedAt < CHECK_INTERVAL_MS) {
131
+ return {
132
+ currentVersion,
133
+ latestVersion: cached.latestVersion,
134
+ updateAvailable: isNewer(cached.latestVersion, currentVersion),
135
+ };
136
+ }
137
+ // Fetch from npm (non-blocking — don't slow down startup)
138
+ const latestVersion = await fetchLatestVersion();
139
+ if (!latestVersion)
140
+ return null;
141
+ writeCache({ latestVersion, checkedAt: Date.now() });
142
+ return {
143
+ currentVersion,
144
+ latestVersion,
145
+ updateAvailable: isNewer(latestVersion, currentVersion),
146
+ };
147
+ }
148
+ // ---------------------------------------------------------------------------
149
+ // Display helpers
150
+ // ---------------------------------------------------------------------------
151
+ const c = {
152
+ reset: '\x1b[0m',
153
+ bold: '\x1b[1m',
154
+ dim: '\x1b[2m',
155
+ yellow: '\x1b[33m',
156
+ cyan: '\x1b[36m',
157
+ green: '\x1b[32m',
158
+ };
159
+ /**
160
+ * Print an update notification to stderr if a newer version is available.
161
+ * Designed to be non-intrusive — just a single line after the startup banner.
162
+ */
163
+ export function printUpdateNotification(result) {
164
+ if (!result?.updateAvailable)
165
+ return;
166
+ console.log(`\n${c.yellow}${c.bold}Update available:${c.reset} ${c.dim}v${result.currentVersion}${c.reset} → ${c.green}v${result.latestVersion}${c.reset}` +
167
+ ` ${c.dim}Run${c.reset} ${c.cyan}npm i -g @renseiai/agentfactory-cli@latest${c.reset} ${c.dim}to update${c.reset}\n`);
168
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Worker Fleet Runner — Programmatic API for the worker fleet manager CLI.
3
+ *
4
+ * Spawns and manages multiple worker processes for parallel agent execution.
5
+ * Each worker runs as a separate OS process with its own resources.
6
+ */
7
+ export interface FleetRunnerConfig {
8
+ /** Number of worker processes (default: CPU cores / 2) */
9
+ workers?: number;
10
+ /** Agents per worker (default: 3) */
11
+ capacity?: number;
12
+ /** Show configuration without starting workers (default: false) */
13
+ dryRun?: boolean;
14
+ /** Coordinator API URL (required) */
15
+ apiUrl: string;
16
+ /** API key for authentication (required) */
17
+ apiKey: string;
18
+ /** Path to the worker script/binary (default: auto-detect from this package) */
19
+ workerScript?: string;
20
+ /** Linear project names for workers to accept (undefined = all) */
21
+ projects?: string[];
22
+ /** Enable auto-update (CLI flag override) */
23
+ autoUpdate?: boolean;
24
+ }
25
+ /**
26
+ * Run a fleet of worker processes.
27
+ *
28
+ * The caller can cancel via the optional {@link AbortSignal}. The function
29
+ * returns once all workers have been stopped.
30
+ */
31
+ export declare function runWorkerFleet(config: FleetRunnerConfig, signal?: AbortSignal): Promise<void>;
32
+ //# sourceMappingURL=worker-fleet-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-fleet-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/worker-fleet-runner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAcH,MAAM,WAAW,iBAAiB;IAChC,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mEAAmE;IACnE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AA6UD;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,iBAAiB,EACzB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAqBf"}
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Worker Fleet Runner — Programmatic API for the worker fleet manager CLI.
3
+ *
4
+ * Spawns and manages multiple worker processes for parallel agent execution.
5
+ * Each worker runs as a separate OS process with its own resources.
6
+ */
7
+ import { spawn } from 'child_process';
8
+ import fs from 'fs';
9
+ import os from 'os';
10
+ import path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+ import { getVersion, checkForUpdate, printUpdateNotification } from './version.js';
13
+ import { maybeAutoUpdate, isAutoUpdateEnabled } from './auto-updater.js';
14
+ // ---------------------------------------------------------------------------
15
+ // ANSI colors
16
+ // ---------------------------------------------------------------------------
17
+ const colors = {
18
+ reset: '\x1b[0m',
19
+ red: '\x1b[31m',
20
+ green: '\x1b[32m',
21
+ yellow: '\x1b[33m',
22
+ blue: '\x1b[34m',
23
+ magenta: '\x1b[35m',
24
+ cyan: '\x1b[36m',
25
+ gray: '\x1b[90m',
26
+ };
27
+ const workerColors = [
28
+ colors.cyan,
29
+ colors.magenta,
30
+ colors.yellow,
31
+ colors.green,
32
+ colors.blue,
33
+ ];
34
+ // ---------------------------------------------------------------------------
35
+ // Helpers
36
+ // ---------------------------------------------------------------------------
37
+ function timestamp() {
38
+ return new Date().toLocaleTimeString('en-US', { hour12: false });
39
+ }
40
+ function fleetLog(workerId, color, level, message) {
41
+ const prefix = workerId !== null
42
+ ? `[W${workerId.toString().padStart(2, '0')}]`
43
+ : '[FLEET]';
44
+ const levelColor = level === 'ERR' ? colors.red : level === 'WRN' ? colors.yellow : colors.gray;
45
+ console.log(`${colors.gray}${timestamp()}${colors.reset} ${color}${prefix}${colors.reset} ${levelColor}${level}${colors.reset} ${message}`);
46
+ }
47
+ function getDefaultWorkerScript() {
48
+ const __filename = fileURLToPath(import.meta.url);
49
+ const __dirname = path.dirname(__filename);
50
+ // Runner lives in lib/, worker entry is one level up.
51
+ // When running from compiled dist/ the .js file exists; when running from
52
+ // source via tsx only the .ts file exists.
53
+ const jsPath = path.resolve(__dirname, '..', 'worker.js');
54
+ if (fs.existsSync(jsPath))
55
+ return jsPath;
56
+ return path.resolve(__dirname, '..', 'worker.ts');
57
+ }
58
+ // ---------------------------------------------------------------------------
59
+ // WorkerFleet class (internal)
60
+ // ---------------------------------------------------------------------------
61
+ class WorkerFleet {
62
+ workers = new Map();
63
+ fleetConfig;
64
+ shuttingDown = false;
65
+ workerScript;
66
+ autoUpdateFlag;
67
+ resolveRunning = null;
68
+ updateInterval = null;
69
+ constructor(fleetConfig, workerScript, autoUpdateFlag) {
70
+ this.fleetConfig = fleetConfig;
71
+ this.workerScript = workerScript;
72
+ this.autoUpdateFlag = autoUpdateFlag;
73
+ }
74
+ async start(signal) {
75
+ const { workers, capacity, dryRun } = this.fleetConfig;
76
+ const totalCapacity = workers * capacity;
77
+ const version = getVersion();
78
+ console.log(`
79
+ ${colors.cyan}================================================================${colors.reset}
80
+ ${colors.cyan} AgentFactory Worker Fleet Manager${colors.reset} ${colors.gray}v${version}${colors.reset}
81
+ ${colors.cyan}================================================================${colors.reset}
82
+ Workers: ${colors.green}${workers}${colors.reset}
83
+ Capacity/Worker: ${colors.green}${capacity}${colors.reset}
84
+ Total Capacity: ${colors.green}${totalCapacity}${colors.reset} concurrent agents
85
+ Projects: ${colors.green}${this.fleetConfig.projects?.length ? this.fleetConfig.projects.join(', ') : 'all'}${colors.reset}
86
+ Auto-update: ${isAutoUpdateEnabled(this.autoUpdateFlag) ? `${colors.green}enabled${colors.reset}` : `${colors.gray}disabled${colors.reset}`}
87
+
88
+ System:
89
+ CPU Cores: ${os.cpus().length}
90
+ Total RAM: ${Math.round(os.totalmem() / 1024 / 1024 / 1024)} GB
91
+ Free RAM: ${Math.round(os.freemem() / 1024 / 1024 / 1024)} GB
92
+ ${colors.cyan}================================================================${colors.reset}
93
+ `);
94
+ // Update check
95
+ const updateCheck = await checkForUpdate();
96
+ printUpdateNotification(updateCheck);
97
+ if (dryRun) {
98
+ console.log(`${colors.yellow}Dry run mode - not starting workers${colors.reset}`);
99
+ return;
100
+ }
101
+ // Wire up AbortSignal for graceful shutdown
102
+ const onAbort = () => this.shutdown('AbortSignal');
103
+ signal?.addEventListener('abort', onAbort, { once: true });
104
+ try {
105
+ // Spawn workers with staggered start to avoid thundering herd
106
+ for (let i = 0; i < workers; i++) {
107
+ if (signal?.aborted)
108
+ break;
109
+ await this.spawnWorker(i);
110
+ if (i < workers - 1) {
111
+ await new Promise((resolve) => setTimeout(resolve, 1000));
112
+ }
113
+ }
114
+ if (signal?.aborted)
115
+ return;
116
+ fleetLog(null, colors.green, 'INF', `All ${workers} workers started`);
117
+ // Periodic auto-update check (every 4 hours)
118
+ if (isAutoUpdateEnabled(this.autoUpdateFlag)) {
119
+ this.updateInterval = setInterval(async () => {
120
+ const check = await checkForUpdate();
121
+ await maybeAutoUpdate(check, {
122
+ cliFlag: this.autoUpdateFlag,
123
+ hasActiveWorkers: async () => this.workers.size > 0 && !this.shuttingDown,
124
+ onBeforeRestart: async () => this.shutdown('auto-update'),
125
+ });
126
+ }, 4 * 60 * 60 * 1000); // 4 hours
127
+ }
128
+ // Keep the fleet manager running until shutdown
129
+ await new Promise((resolve) => {
130
+ this.resolveRunning = resolve;
131
+ });
132
+ }
133
+ finally {
134
+ if (this.updateInterval)
135
+ clearInterval(this.updateInterval);
136
+ signal?.removeEventListener('abort', onAbort);
137
+ }
138
+ }
139
+ async spawnWorker(id) {
140
+ const color = workerColors[id % workerColors.length];
141
+ const existingWorker = this.workers.get(id);
142
+ const restartCount = existingWorker?.restartCount ?? 0;
143
+ fleetLog(id, color, 'INF', `Starting worker (capacity: ${this.fleetConfig.capacity})${restartCount > 0 ? ` [restart #${restartCount}]` : ''}`);
144
+ const nodeArgs = [];
145
+ // When running a .ts worker script, register tsx so Node can load it
146
+ if (this.workerScript.endsWith('.ts')) {
147
+ nodeArgs.push('--import', 'tsx');
148
+ }
149
+ nodeArgs.push(this.workerScript, '--capacity', String(this.fleetConfig.capacity), '--api-url', this.fleetConfig.apiUrl, '--api-key', this.fleetConfig.apiKey);
150
+ if (this.fleetConfig.projects?.length) {
151
+ nodeArgs.push('--projects', this.fleetConfig.projects.join(','));
152
+ }
153
+ const workerProcess = spawn('node', nodeArgs, {
154
+ stdio: ['ignore', 'pipe', 'pipe'],
155
+ env: {
156
+ ...process.env,
157
+ WORKER_FLEET_ID: String(id),
158
+ },
159
+ cwd: process.cwd(),
160
+ });
161
+ const workerInfo = {
162
+ id,
163
+ process: workerProcess,
164
+ color,
165
+ startedAt: new Date(),
166
+ restartCount,
167
+ };
168
+ this.workers.set(id, workerInfo);
169
+ // Handle stdout — prefix with worker ID
170
+ workerProcess.stdout?.on('data', (data) => {
171
+ const lines = data.toString().trim().split('\n');
172
+ for (const line of lines) {
173
+ if (line.trim()) {
174
+ console.log(`${color}[W${id.toString().padStart(2, '0')}]${colors.reset} ${line}`);
175
+ }
176
+ }
177
+ });
178
+ // Handle stderr
179
+ workerProcess.stderr?.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} ${colors.red}${line}${colors.reset}`);
184
+ }
185
+ }
186
+ });
187
+ // Handle worker exit
188
+ workerProcess.on('exit', (code, sig) => {
189
+ if (this.shuttingDown) {
190
+ fleetLog(id, color, 'INF', `Worker stopped (code: ${code}, signal: ${sig})`);
191
+ return;
192
+ }
193
+ fleetLog(id, color, 'WRN', `Worker exited unexpectedly (code: ${code}, signal: ${sig}) - restarting in 5s`);
194
+ const worker = this.workers.get(id);
195
+ if (worker) {
196
+ worker.restartCount++;
197
+ }
198
+ setTimeout(() => {
199
+ if (!this.shuttingDown) {
200
+ this.spawnWorker(id);
201
+ }
202
+ }, 5000);
203
+ });
204
+ workerProcess.on('error', (err) => {
205
+ fleetLog(id, color, 'ERR', `Worker error: ${err.message}`);
206
+ });
207
+ }
208
+ async shutdown(reason) {
209
+ if (this.shuttingDown)
210
+ return;
211
+ this.shuttingDown = true;
212
+ console.log(`\n${colors.yellow}Received ${reason} - shutting down fleet...${colors.reset}`);
213
+ for (const [id, worker] of this.workers) {
214
+ fleetLog(id, worker.color, 'INF', 'Stopping worker...');
215
+ worker.process.kill('SIGTERM');
216
+ }
217
+ // Wait for workers to exit (max 30 seconds)
218
+ const forceKillTimeout = setTimeout(() => {
219
+ console.log(`${colors.red}Timeout waiting for workers - force killing${colors.reset}`);
220
+ for (const worker of this.workers.values()) {
221
+ worker.process.kill('SIGKILL');
222
+ }
223
+ }, 30000);
224
+ await Promise.all(Array.from(this.workers.values()).map((worker) => new Promise((resolve) => {
225
+ worker.process.on('exit', () => resolve());
226
+ })));
227
+ clearTimeout(forceKillTimeout);
228
+ console.log(`${colors.green}All workers stopped${colors.reset}`);
229
+ // Resolve the running promise so start() returns
230
+ this.resolveRunning?.();
231
+ }
232
+ }
233
+ // ---------------------------------------------------------------------------
234
+ // Runner
235
+ // ---------------------------------------------------------------------------
236
+ /**
237
+ * Run a fleet of worker processes.
238
+ *
239
+ * The caller can cancel via the optional {@link AbortSignal}. The function
240
+ * returns once all workers have been stopped.
241
+ */
242
+ export async function runWorkerFleet(config, signal) {
243
+ const workers = config.workers ?? Math.max(1, Math.floor(os.cpus().length / 2));
244
+ const capacity = config.capacity ?? 3;
245
+ const dryRun = config.dryRun ?? false;
246
+ const workerScript = config.workerScript ?? getDefaultWorkerScript();
247
+ const fleet = new WorkerFleet({
248
+ workers,
249
+ capacity,
250
+ dryRun,
251
+ apiUrl: config.apiUrl,
252
+ apiKey: config.apiKey,
253
+ projects: config.projects?.length ? config.projects : undefined,
254
+ }, workerScript, config.autoUpdate);
255
+ await fleet.start(signal);
256
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Worker Runner — Programmatic API for the remote worker CLI.
3
+ *
4
+ * Encapsulates all global state into the runner function's closure so that
5
+ * multiple workers can be started from the same process (e.g. tests) without
6
+ * leaking state between invocations.
7
+ */
8
+ export interface WorkerRunnerConfig {
9
+ /** Coordinator API URL */
10
+ apiUrl: string;
11
+ /** API key for authentication */
12
+ apiKey: string;
13
+ /** Worker hostname (default: os.hostname()) */
14
+ hostname?: string;
15
+ /** Maximum concurrent agents (default: 3) */
16
+ capacity?: number;
17
+ /** Poll but don't execute work (default: false) */
18
+ dryRun?: boolean;
19
+ /** Linear API key for agent operations (default: process.env.LINEAR_API_KEY) */
20
+ linearApiKey?: string;
21
+ /** Git repository root (default: auto-detect) */
22
+ gitRoot?: string;
23
+ /** Linear project names to accept (undefined = all) */
24
+ projects?: string[];
25
+ }
26
+ /**
27
+ * Run a worker that polls the coordinator for work and executes agents.
28
+ *
29
+ * All state is encapsulated in the function closure. The caller can cancel
30
+ * via the optional {@link AbortSignal}.
31
+ */
32
+ export declare function runWorker(config: WorkerRunnerConfig, signal?: AbortSignal): Promise<void>;
33
+ //# sourceMappingURL=worker-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/worker-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAmBH,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mDAAmD;IACnD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,gFAAgF;IAChF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAwED;;;;;GAKG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,kBAAkB,EAC1B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAm7Bf"}