@sylphx/flow 1.8.0 → 1.8.2

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 (126) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/assets/output-styles/silent.md +145 -8
  3. package/assets/rules/core.md +19 -2
  4. package/package.json +2 -12
  5. package/src/commands/flow/execute.ts +470 -0
  6. package/src/commands/flow/index.ts +11 -0
  7. package/src/commands/flow/prompt.ts +35 -0
  8. package/src/commands/flow/setup.ts +312 -0
  9. package/src/commands/flow/targets.ts +18 -0
  10. package/src/commands/flow/types.ts +47 -0
  11. package/src/commands/flow-command.ts +18 -967
  12. package/src/commands/flow-orchestrator.ts +14 -5
  13. package/src/commands/hook-command.ts +1 -1
  14. package/src/commands/init-core.ts +12 -3
  15. package/src/commands/run-command.ts +1 -1
  16. package/src/config/rules.ts +1 -1
  17. package/src/core/error-handling.ts +1 -1
  18. package/src/core/loop-controller.ts +1 -1
  19. package/src/core/state-detector.ts +1 -1
  20. package/src/core/target-manager.ts +1 -1
  21. package/src/index.ts +1 -1
  22. package/src/shared/files/index.ts +1 -1
  23. package/src/shared/processing/index.ts +1 -1
  24. package/src/targets/claude-code.ts +3 -3
  25. package/src/targets/opencode.ts +3 -3
  26. package/src/utils/agent-enhancer.ts +2 -2
  27. package/src/utils/{mcp-config.ts → config/mcp-config.ts} +4 -4
  28. package/src/utils/{paths.ts → config/paths.ts} +1 -1
  29. package/src/utils/{settings.ts → config/settings.ts} +1 -1
  30. package/src/utils/{target-config.ts → config/target-config.ts} +5 -5
  31. package/src/utils/{target-utils.ts → config/target-utils.ts} +3 -3
  32. package/src/utils/display/banner.ts +25 -0
  33. package/src/utils/display/status.ts +55 -0
  34. package/src/utils/{file-operations.ts → files/file-operations.ts} +2 -2
  35. package/src/utils/files/jsonc.ts +36 -0
  36. package/src/utils/{sync-utils.ts → files/sync-utils.ts} +3 -3
  37. package/src/utils/index.ts +42 -61
  38. package/src/utils/version.ts +47 -0
  39. package/src/components/benchmark-monitor.tsx +0 -331
  40. package/src/components/reindex-progress.tsx +0 -261
  41. package/src/composables/functional/index.ts +0 -14
  42. package/src/composables/functional/useEnvironment.ts +0 -171
  43. package/src/composables/functional/useFileSystem.ts +0 -139
  44. package/src/composables/index.ts +0 -4
  45. package/src/composables/useEnv.ts +0 -13
  46. package/src/composables/useRuntimeConfig.ts +0 -27
  47. package/src/core/ai-sdk.ts +0 -603
  48. package/src/core/app-factory.ts +0 -381
  49. package/src/core/builtin-agents.ts +0 -9
  50. package/src/core/command-system.ts +0 -550
  51. package/src/core/config-system.ts +0 -550
  52. package/src/core/connection-pool.ts +0 -390
  53. package/src/core/di-container.ts +0 -155
  54. package/src/core/headless-display.ts +0 -96
  55. package/src/core/interfaces/index.ts +0 -22
  56. package/src/core/interfaces/repository.interface.ts +0 -91
  57. package/src/core/interfaces/service.interface.ts +0 -133
  58. package/src/core/interfaces.ts +0 -96
  59. package/src/core/result.ts +0 -351
  60. package/src/core/service-config.ts +0 -252
  61. package/src/core/session-service.ts +0 -121
  62. package/src/core/storage-factory.ts +0 -115
  63. package/src/core/stream-handler.ts +0 -288
  64. package/src/core/type-utils.ts +0 -427
  65. package/src/core/unified-storage.ts +0 -456
  66. package/src/core/validation/limit.ts +0 -46
  67. package/src/core/validation/query.ts +0 -20
  68. package/src/db/auto-migrate.ts +0 -322
  69. package/src/db/base-database-client.ts +0 -144
  70. package/src/db/cache-db.ts +0 -218
  71. package/src/db/cache-schema.ts +0 -75
  72. package/src/db/database.ts +0 -70
  73. package/src/db/index.ts +0 -252
  74. package/src/db/memory-db.ts +0 -153
  75. package/src/db/memory-schema.ts +0 -29
  76. package/src/db/schema.ts +0 -289
  77. package/src/db/session-repository.ts +0 -733
  78. package/src/domains/index.ts +0 -6
  79. package/src/domains/utilities/index.ts +0 -6
  80. package/src/domains/utilities/time/index.ts +0 -5
  81. package/src/domains/utilities/time/tools.ts +0 -291
  82. package/src/services/agent-service.ts +0 -273
  83. package/src/services/evaluation-service.ts +0 -271
  84. package/src/services/functional/evaluation-logic.ts +0 -296
  85. package/src/services/functional/file-processor.ts +0 -273
  86. package/src/services/functional/index.ts +0 -12
  87. package/src/services/memory.service.ts +0 -476
  88. package/src/types/api/batch.ts +0 -108
  89. package/src/types/api/errors.ts +0 -118
  90. package/src/types/api/index.ts +0 -55
  91. package/src/types/api/requests.ts +0 -76
  92. package/src/types/api/responses.ts +0 -180
  93. package/src/types/api/websockets.ts +0 -85
  94. package/src/types/benchmark.ts +0 -49
  95. package/src/types/database.types.ts +0 -510
  96. package/src/types/memory-types.ts +0 -63
  97. package/src/utils/advanced-tokenizer.ts +0 -191
  98. package/src/utils/ai-model-fetcher.ts +0 -19
  99. package/src/utils/async-file-operations.ts +0 -516
  100. package/src/utils/audio-player.ts +0 -345
  101. package/src/utils/codebase-helpers.ts +0 -211
  102. package/src/utils/console-ui.ts +0 -79
  103. package/src/utils/database-errors.ts +0 -140
  104. package/src/utils/debug-logger.ts +0 -49
  105. package/src/utils/file-scanner.ts +0 -259
  106. package/src/utils/help.ts +0 -20
  107. package/src/utils/immutable-cache.ts +0 -106
  108. package/src/utils/jsonc.ts +0 -158
  109. package/src/utils/memory-tui.ts +0 -414
  110. package/src/utils/models-dev.ts +0 -91
  111. package/src/utils/parallel-operations.ts +0 -487
  112. package/src/utils/process-manager.ts +0 -155
  113. package/src/utils/prompts.ts +0 -120
  114. package/src/utils/search-tool-builder.ts +0 -214
  115. package/src/utils/session-manager.ts +0 -168
  116. package/src/utils/session-title.ts +0 -87
  117. package/src/utils/simplified-errors.ts +0 -410
  118. package/src/utils/template-engine.ts +0 -94
  119. package/src/utils/test-audio.ts +0 -71
  120. package/src/utils/todo-context.ts +0 -46
  121. package/src/utils/token-counter.ts +0 -288
  122. /package/src/utils/{cli-output.ts → display/cli-output.ts} +0 -0
  123. /package/src/utils/{logger.ts → display/logger.ts} +0 -0
  124. /package/src/utils/{notifications.ts → display/notifications.ts} +0 -0
  125. /package/src/utils/{secret-utils.ts → security/secret-utils.ts} +0 -0
  126. /package/src/utils/{security.ts → security/security.ts} +0 -0
@@ -1,487 +0,0 @@
1
- /**
2
- * Parallel Operations Utility
3
- * Provides utilities for executing async operations in parallel with proper error handling and resource management
4
- */
5
-
6
- import { chunk } from './functional/array.js';
7
- import { logger } from './logger.js';
8
-
9
- /**
10
- * Configuration for parallel operations
11
- */
12
- export interface ParallelOptions {
13
- /** Maximum number of concurrent operations */
14
- concurrency?: number;
15
- /** Whether to continue on error or stop immediately */
16
- continueOnError?: boolean;
17
- /** Timeout for individual operations (ms) */
18
- timeout?: number;
19
- /** Delay between batches (ms) */
20
- batchDelay?: number;
21
- /** Progress callback */
22
- onProgress?: (completed: number, total: number, current?: unknown) => void;
23
- }
24
-
25
- /**
26
- * Result of a parallel operation
27
- */
28
- export interface ParallelResult<T> {
29
- /** Successful results */
30
- successful: Array<{ index: number; result: T; item: unknown }>;
31
- /** Failed operations */
32
- failed: Array<{ index: number; error: Error; item: unknown }>;
33
- /** Total number of operations */
34
- total: number;
35
- /** Success count */
36
- successCount: number;
37
- /** Failure count */
38
- failureCount: number;
39
- /** Execution time in milliseconds */
40
- duration: number;
41
- }
42
-
43
- /**
44
- * Batch operation configuration
45
- */
46
- export interface BatchOptions<T> extends ParallelOptions {
47
- /** Function to process each batch */
48
- processor: (batch: T[], batchIndex: number) => Promise<unknown[]>;
49
- /** Batch size */
50
- batchSize?: number;
51
- }
52
-
53
- /**
54
- * Execute operations in parallel with controlled concurrency
55
- */
56
- export async function parallel<T>(
57
- items: unknown[],
58
- operation: (item: unknown, index: number) => Promise<T>,
59
- options: ParallelOptions = {}
60
- ): Promise<ParallelResult<T>> {
61
- const startTime = Date.now();
62
- const { concurrency = 10, continueOnError = true, timeout = 30000, onProgress } = options;
63
-
64
- logger.debug('Starting parallel operations', {
65
- itemCount: items.length,
66
- concurrency,
67
- continueOnError,
68
- timeout,
69
- });
70
-
71
- // FUNCTIONAL: Use chunk and reduce to accumulate results instead of imperative loop
72
- const batches = chunk(concurrency)(items);
73
-
74
- const results = await batches.reduce<Promise<ParallelResult<T>>>(
75
- async (accPromise, batch, batchIdx) => {
76
- const acc = await accPromise;
77
-
78
- const batchPromises = batch.map(async (item, batchIndex) => {
79
- const globalIndex = batchIdx * concurrency + batchIndex;
80
-
81
- try {
82
- // Add timeout to individual operations
83
- const operationPromise = operation(item, globalIndex);
84
- const timeoutPromise = new Promise<never>((_, reject) => {
85
- setTimeout(() => reject(new Error(`Operation timeout after ${timeout}ms`)), timeout);
86
- });
87
-
88
- const result = await Promise.race([operationPromise, timeoutPromise]);
89
-
90
- // Report progress
91
- if (onProgress) {
92
- onProgress(acc.successCount + acc.failureCount + 1, items.length, item);
93
- }
94
-
95
- return { success: true as const, index: globalIndex, result, item };
96
- } catch (error) {
97
- const errorObj = error instanceof Error ? error : new Error(String(error));
98
-
99
- logger.warn('Parallel operation failed', {
100
- index: globalIndex,
101
- error: errorObj.message,
102
- item: typeof item === 'object' ? JSON.stringify(item) : item,
103
- });
104
-
105
- // Re-throw if not continuing on error
106
- if (!continueOnError) {
107
- throw errorObj;
108
- }
109
-
110
- return { success: false as const, index: globalIndex, error: errorObj, item };
111
- }
112
- });
113
-
114
- // Wait for current batch to complete
115
- const batchResults = await Promise.all(batchPromises);
116
-
117
- // FUNCTIONAL: Partition results into successful/failed
118
- const newSuccessful = batchResults
119
- .filter((r) => r.success)
120
- .map((r) => ({ index: r.index, result: r.result, item: r.item }));
121
- const newFailed = batchResults
122
- .filter((r) => !r.success)
123
- .map((r) => ({ index: r.index, error: r.error, item: r.item }));
124
-
125
- // Add delay between batches if specified
126
- if (options.batchDelay && batchIdx < batches.length - 1) {
127
- await new Promise((resolve) => setTimeout(resolve, options.batchDelay!));
128
- }
129
-
130
- return {
131
- successful: [...acc.successful, ...newSuccessful],
132
- failed: [...acc.failed, ...newFailed],
133
- total: items.length,
134
- successCount: acc.successCount + newSuccessful.length,
135
- failureCount: acc.failureCount + newFailed.length,
136
- duration: 0, // Will be set after reduce completes
137
- };
138
- },
139
- Promise.resolve({
140
- successful: [],
141
- failed: [],
142
- total: items.length,
143
- successCount: 0,
144
- failureCount: 0,
145
- duration: 0,
146
- })
147
- );
148
-
149
- // Set final duration
150
- results.duration = Date.now() - startTime;
151
-
152
- logger.info('Parallel operations completed', {
153
- total: results.total,
154
- successCount: results.successCount,
155
- failureCount: results.failureCount,
156
- duration: results.duration,
157
- successRate: `${((results.successCount / results.total) * 100).toFixed(1)}%`,
158
- });
159
-
160
- return results;
161
- }
162
-
163
- /**
164
- * Execute operations in parallel with automatic retry for failed operations
165
- */
166
- export async function parallelWithRetry<T>(
167
- items: unknown[],
168
- operation: (item: unknown, index: number) => Promise<T>,
169
- options: ParallelOptions & { maxRetries?: number; retryDelay?: number } = {}
170
- ): Promise<ParallelResult<T>> {
171
- const { maxRetries = 3, retryDelay = 1000, ...parallelOptions } = options;
172
-
173
- const result = await parallel(items, operation, parallelOptions);
174
-
175
- // FUNCTIONAL: Retry failed operations using reduce instead of for loop
176
- const attempts = Array.from({ length: maxRetries }, (_, i) => i + 1);
177
-
178
- const finalResult = await attempts.reduce<Promise<ParallelResult<T>>>(
179
- async (accPromise, attempt) => {
180
- const acc = await accPromise;
181
-
182
- if (acc.failed.length === 0) {
183
- return acc; // No more failures to retry
184
- }
185
-
186
- logger.info(`Retrying failed operations (attempt ${attempt}/${maxRetries})`, {
187
- failedCount: acc.failed.length,
188
- retryDelay,
189
- });
190
-
191
- // Wait before retry
192
- if (retryDelay > 0) {
193
- await new Promise((resolve) => setTimeout(resolve, retryDelay));
194
- }
195
-
196
- // Retry only failed items
197
- const failedItems = acc.failed.map((f) => f.item);
198
- const retryResult = await parallel(failedItems, operation, parallelOptions);
199
-
200
- // Merge results immutably
201
- return {
202
- successful: [...acc.successful, ...retryResult.successful],
203
- failed: retryResult.failed,
204
- total: acc.total,
205
- successCount: acc.successful.length + retryResult.successful.length,
206
- failureCount: retryResult.failed.length,
207
- duration: acc.duration,
208
- };
209
- },
210
- Promise.resolve(result)
211
- );
212
-
213
- return finalResult;
214
- }
215
-
216
- /**
217
- * Process items in batches with parallel execution within each batch
218
- */
219
- export async function batchParallel<T, R>(
220
- items: T[],
221
- processor: (batch: T[], batchIndex: number) => Promise<R[]>,
222
- options: BatchOptions<T> = {}
223
- ): Promise<R[]> {
224
- const { batchSize = 50, concurrency = 3, continueOnError = true, onProgress } = options;
225
-
226
- // FUNCTIONAL: Use chunk utility instead of for loop
227
- const batches = chunk(batchSize)(items);
228
-
229
- logger.info('Starting batch parallel processing', {
230
- totalItems: items.length,
231
- batchSize,
232
- batchCount: batches.length,
233
- concurrency,
234
- });
235
-
236
- const processBatch = async (batch: T[], batchIndex: number): Promise<R[]> => {
237
- try {
238
- const result = await processor(batch, batchIndex);
239
-
240
- if (onProgress) {
241
- onProgress((batchIndex + 1) * batchSize, items.length, batch);
242
- }
243
-
244
- return result;
245
- } catch (error) {
246
- logger.error('Batch processing failed', {
247
- batchIndex,
248
- batchSize: batch.length,
249
- error: (error as Error).message,
250
- });
251
-
252
- if (!continueOnError) {
253
- throw error;
254
- }
255
-
256
- return [];
257
- }
258
- };
259
-
260
- // Process batches in parallel
261
- const results = await parallel(batches, (batch, index) => processBatch(batch as T[], index), {
262
- concurrency,
263
- continueOnError,
264
- onProgress,
265
- });
266
-
267
- // Flatten successful results
268
- return results.successful.flatMap((r) => r.result as R[]);
269
- }
270
-
271
- /**
272
- * Parallel map operation
273
- */
274
- export async function parallelMap<T, R>(
275
- items: T[],
276
- mapper: (item: T, index: number) => Promise<R>,
277
- options?: ParallelOptions
278
- ): Promise<R[]> {
279
- const result = await parallel(items, mapper, options);
280
-
281
- if (result.failureCount > 0) {
282
- logger.warn('parallelMap had failures', {
283
- total: result.total,
284
- failures: result.failureCount,
285
- });
286
- }
287
-
288
- // Return results in original order
289
- const orderedResults = new Array(result.total);
290
- result.successful.forEach(({ index, result }) => {
291
- orderedResults[index] = result;
292
- });
293
-
294
- return orderedResults.filter(Boolean);
295
- }
296
-
297
- /**
298
- * Parallel filter operation
299
- */
300
- export async function parallelFilter<T>(
301
- items: T[],
302
- predicate: (item: T, index: number) => Promise<boolean>,
303
- options?: ParallelOptions
304
- ): Promise<T[]> {
305
- const results = await parallelMap(
306
- items,
307
- async (item, index) => ({
308
- item,
309
- passes: await predicate(item, index),
310
- }),
311
- options
312
- );
313
-
314
- return results.filter((r) => r.passes).map((r) => r.item);
315
- }
316
-
317
- /**
318
- * Parallel reduce operation (for associative operations)
319
- */
320
- export async function parallelReduce<T>(
321
- items: T[],
322
- reducer: (acc: T, item: T) => Promise<T>,
323
- initialValue: T,
324
- options?: ParallelOptions
325
- ): Promise<T> {
326
- if (items.length === 0) {
327
- return initialValue;
328
- }
329
-
330
- // For small arrays, use regular reduce
331
- if (items.length <= 100) {
332
- // FUNCTIONAL: Use reduce instead of for-of loop
333
- return await items.reduce(async (accPromise, item) => {
334
- const acc = await accPromise;
335
- return await reducer(acc, item);
336
- }, Promise.resolve(initialValue));
337
- }
338
-
339
- // For large arrays, split into chunks and reduce in parallel
340
- const chunkSize = Math.ceil(items.length / (options?.concurrency || 10));
341
-
342
- // FUNCTIONAL: Use chunk utility instead of for loop
343
- const chunks = chunk(chunkSize)(items);
344
-
345
- const chunkResults = await parallelMap(
346
- chunks,
347
- async (chunkItems) => {
348
- // FUNCTIONAL: Use reduce instead of for-of loop
349
- return await chunkItems.reduce(async (accPromise, item) => {
350
- const acc = await accPromise;
351
- return await reducer(acc, item);
352
- }, Promise.resolve(initialValue));
353
- },
354
- options
355
- );
356
-
357
- // FUNCTIONAL: Combine chunk results using reduce instead of for-of loop
358
- return await chunkResults.reduce(async (accPromise, chunkResult) => {
359
- const acc = await accPromise;
360
- return await reducer(acc, chunkResult);
361
- }, Promise.resolve(initialValue));
362
- }
363
-
364
- /**
365
- * Execute multiple async operations in parallel and return all results
366
- */
367
- export async function all<T>(operations: Array<() => Promise<T>>): Promise<T[]> {
368
- const promises = operations.map((op) => op());
369
- return Promise.all(promises);
370
- }
371
-
372
- /**
373
- * Execute multiple async operations in parallel and return first successful result
374
- */
375
- export async function any<T>(operations: Array<() => Promise<T>>): Promise<T> {
376
- const promises = operations.map(async (op, index) => {
377
- try {
378
- return { success: true, result: await op(), index };
379
- } catch (error) {
380
- return { success: false, error, index };
381
- }
382
- });
383
-
384
- const results = await Promise.all(promises);
385
- const successful = results.find((r) => r.success);
386
-
387
- if (successful) {
388
- return successful.result as T;
389
- }
390
-
391
- // If none succeeded, throw the first error
392
- const firstFailure = results.find((r) => !r.success);
393
- if (firstFailure) {
394
- throw firstFailure.error;
395
- }
396
-
397
- throw new Error('No operations provided');
398
- }
399
-
400
- /**
401
- * Parallel queue interface
402
- */
403
- export interface ParallelQueueInstance<T> {
404
- add(item: T): Promise<unknown>;
405
- size(): number;
406
- clear(): void;
407
- }
408
-
409
- /**
410
- * Create a parallel execution queue with controlled concurrency
411
- */
412
- export function createParallelQueue<T>(
413
- processor: (item: T) => Promise<unknown>,
414
- concurrency = 10
415
- ): ParallelQueueInstance<T> {
416
- // Closure-based state
417
- const queue: Array<{
418
- item: T;
419
- resolve: (value: unknown) => void;
420
- reject: (error: Error) => void;
421
- }> = [];
422
- let running = 0;
423
-
424
- const process = async (): Promise<void> => {
425
- if (running >= concurrency || queue.length === 0) {
426
- return;
427
- }
428
-
429
- running++;
430
- const { item, resolve, reject } = queue.shift()!;
431
-
432
- try {
433
- const result = await processor(item);
434
- resolve(result);
435
- } catch (error) {
436
- reject(error as Error);
437
- } finally {
438
- running--;
439
- // Process next item in queue
440
- setImmediate(() => process());
441
- }
442
- };
443
-
444
- const add = async (item: T): Promise<unknown> => {
445
- return new Promise((resolve, reject) => {
446
- queue.push({ item, resolve, reject });
447
- process();
448
- });
449
- };
450
-
451
- const size = (): number => {
452
- return queue.length;
453
- };
454
-
455
- const clear = (): void => {
456
- queue.length = 0;
457
- };
458
-
459
- return {
460
- add,
461
- size,
462
- clear,
463
- };
464
- }
465
-
466
- /**
467
- * @deprecated Use createParallelQueue() for new code
468
- */
469
- export class ParallelQueue<T> {
470
- private instance: ParallelQueueInstance<T>;
471
-
472
- constructor(processor: (item: T) => Promise<unknown>, concurrency = 10) {
473
- this.instance = createParallelQueue(processor, concurrency);
474
- }
475
-
476
- async add(item: T): Promise<unknown> {
477
- return this.instance.add(item);
478
- }
479
-
480
- size(): number {
481
- return this.instance.size();
482
- }
483
-
484
- clear(): void {
485
- return this.instance.clear();
486
- }
487
- }
@@ -1,155 +0,0 @@
1
- /**
2
- * ProcessManager interface for managing child processes
3
- */
4
- export interface ProcessManager {
5
- trackChildProcess(childProcess: any): void;
6
- killAllProcesses(): Promise<void>;
7
- // Internal for testing - exposed for tests to access state
8
- readonly _state?: ProcessManagerState;
9
- }
10
-
11
- /**
12
- * Internal state for ProcessManager
13
- */
14
- interface ProcessManagerState {
15
- readonly childProcesses: Set<any>;
16
- isShuttingDown: boolean;
17
- readonly signalHandlers: Map<string, (...args: any[]) => void>;
18
- }
19
-
20
- /**
21
- * Create a ProcessManager instance
22
- */
23
- export function createProcessManager(): ProcessManager {
24
- const state: ProcessManagerState = {
25
- childProcesses: new Set(),
26
- isShuttingDown: false,
27
- signalHandlers: new Map(),
28
- };
29
-
30
- /**
31
- * Cleanup signal handlers and reset state (for testing)
32
- */
33
- const cleanup = (): void => {
34
- // Remove signal handlers
35
- for (const [signal, handler] of state.signalHandlers.entries()) {
36
- process.removeListener(signal as any, handler);
37
- }
38
- state.signalHandlers.clear();
39
-
40
- // Clear child processes
41
- state.childProcesses.clear();
42
- state.isShuttingDown = false;
43
- };
44
-
45
- /**
46
- * Kill all tracked child processes
47
- */
48
- const killAllProcesses = async (): Promise<void> => {
49
- const killPromises = Array.from(state.childProcesses).map(async (childProcess) => {
50
- try {
51
- if (childProcess && !childProcess.killed) {
52
- childProcess.kill('SIGTERM');
53
-
54
- // Force kill if it doesn't stop after 2 seconds
55
- setTimeout(() => {
56
- if (!childProcess.killed) {
57
- childProcess.kill('SIGKILL');
58
- }
59
- }, 2000);
60
- }
61
- } catch (_error) {
62
- // Silently handle kill errors
63
- }
64
- });
65
-
66
- await Promise.all(killPromises);
67
- state.childProcesses.clear();
68
- };
69
-
70
- /**
71
- * Setup signal handlers for graceful shutdown
72
- */
73
- const setupSignalHandlers = (): void => {
74
- const shutdown = async (_signal: string) => {
75
- if (state.isShuttingDown) {
76
- return;
77
- }
78
- state.isShuttingDown = true;
79
-
80
- await killAllProcesses();
81
- process.exit(0);
82
- };
83
-
84
- // Create and store signal handlers
85
- const sigintHandler = () => shutdown('SIGINT');
86
- const sigtermHandler = () => shutdown('SIGTERM');
87
- const sighupHandler = () => shutdown('SIGHUP');
88
-
89
- state.signalHandlers.set('SIGINT', sigintHandler);
90
- state.signalHandlers.set('SIGTERM', sigtermHandler);
91
- state.signalHandlers.set('SIGHUP', sighupHandler);
92
-
93
- // Handle termination signals
94
- process.on('SIGINT', sigintHandler);
95
- process.on('SIGTERM', sigtermHandler);
96
- process.on('SIGHUP', sighupHandler);
97
- };
98
-
99
- /**
100
- * Track a child process for cleanup on shutdown
101
- */
102
- const trackChildProcess = (childProcess: any): void => {
103
- state.childProcesses.add(childProcess);
104
-
105
- // Remove from tracking when process exits
106
- childProcess.on('exit', () => {
107
- state.childProcesses.delete(childProcess);
108
- });
109
- };
110
-
111
- // Setup signal handlers when instance is created
112
- setupSignalHandlers();
113
-
114
- const manager: ProcessManager & { _cleanup?: () => void; _state?: ProcessManagerState } = {
115
- trackChildProcess,
116
- killAllProcesses,
117
- _state: state, // Expose state for testing
118
- };
119
-
120
- // Expose cleanup for testing
121
- (manager as any)._cleanup = cleanup;
122
-
123
- return manager;
124
- }
125
-
126
- // Singleton instance for backward compatibility
127
- let _processManagerInstance: ProcessManager | null = null;
128
-
129
- /**
130
- * Get the singleton ProcessManager instance
131
- * @deprecated Use createProcessManager() for new code
132
- */
133
- export class ProcessManager {
134
- static getInstance(): ProcessManager {
135
- if (!_processManagerInstance) {
136
- _processManagerInstance = createProcessManager();
137
- }
138
- return _processManagerInstance;
139
- }
140
-
141
- /**
142
- * Reset singleton instance (for testing only)
143
- * @internal
144
- */
145
- static resetInstance(): void {
146
- if (_processManagerInstance) {
147
- // Call cleanup if available
148
- const cleanup = (_processManagerInstance as any)._cleanup;
149
- if (cleanup) {
150
- cleanup();
151
- }
152
- _processManagerInstance = null;
153
- }
154
- }
155
- }