@sparkleideas/shared 3.0.0-alpha.7

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 (96) hide show
  1. package/README.md +323 -0
  2. package/__tests__/hooks/bash-safety.test.ts +289 -0
  3. package/__tests__/hooks/file-organization.test.ts +335 -0
  4. package/__tests__/hooks/git-commit.test.ts +336 -0
  5. package/__tests__/hooks/index.ts +23 -0
  6. package/__tests__/hooks/session-hooks.test.ts +357 -0
  7. package/__tests__/hooks/task-hooks.test.ts +193 -0
  8. package/docs/EVENTS_IMPLEMENTATION_SUMMARY.md +388 -0
  9. package/docs/EVENTS_QUICK_REFERENCE.md +470 -0
  10. package/docs/EVENTS_README.md +352 -0
  11. package/package.json +39 -0
  12. package/src/core/config/defaults.ts +207 -0
  13. package/src/core/config/index.ts +15 -0
  14. package/src/core/config/loader.ts +271 -0
  15. package/src/core/config/schema.ts +188 -0
  16. package/src/core/config/validator.ts +209 -0
  17. package/src/core/event-bus.ts +236 -0
  18. package/src/core/index.ts +22 -0
  19. package/src/core/interfaces/agent.interface.ts +251 -0
  20. package/src/core/interfaces/coordinator.interface.ts +363 -0
  21. package/src/core/interfaces/event.interface.ts +267 -0
  22. package/src/core/interfaces/index.ts +19 -0
  23. package/src/core/interfaces/memory.interface.ts +332 -0
  24. package/src/core/interfaces/task.interface.ts +223 -0
  25. package/src/core/orchestrator/event-coordinator.ts +122 -0
  26. package/src/core/orchestrator/health-monitor.ts +214 -0
  27. package/src/core/orchestrator/index.ts +89 -0
  28. package/src/core/orchestrator/lifecycle-manager.ts +263 -0
  29. package/src/core/orchestrator/session-manager.ts +279 -0
  30. package/src/core/orchestrator/task-manager.ts +317 -0
  31. package/src/events/domain-events.ts +584 -0
  32. package/src/events/event-store.test.ts +387 -0
  33. package/src/events/event-store.ts +588 -0
  34. package/src/events/example-usage.ts +293 -0
  35. package/src/events/index.ts +90 -0
  36. package/src/events/projections.ts +561 -0
  37. package/src/events/state-reconstructor.ts +349 -0
  38. package/src/events.ts +367 -0
  39. package/src/hooks/INTEGRATION.md +658 -0
  40. package/src/hooks/README.md +532 -0
  41. package/src/hooks/example-usage.ts +499 -0
  42. package/src/hooks/executor.ts +379 -0
  43. package/src/hooks/hooks.test.ts +421 -0
  44. package/src/hooks/index.ts +131 -0
  45. package/src/hooks/registry.ts +333 -0
  46. package/src/hooks/safety/bash-safety.ts +604 -0
  47. package/src/hooks/safety/file-organization.ts +473 -0
  48. package/src/hooks/safety/git-commit.ts +623 -0
  49. package/src/hooks/safety/index.ts +46 -0
  50. package/src/hooks/session-hooks.ts +559 -0
  51. package/src/hooks/task-hooks.ts +513 -0
  52. package/src/hooks/types.ts +357 -0
  53. package/src/hooks/verify-exports.test.ts +125 -0
  54. package/src/index.ts +195 -0
  55. package/src/mcp/connection-pool.ts +438 -0
  56. package/src/mcp/index.ts +183 -0
  57. package/src/mcp/server.ts +774 -0
  58. package/src/mcp/session-manager.ts +428 -0
  59. package/src/mcp/tool-registry.ts +566 -0
  60. package/src/mcp/transport/http.ts +557 -0
  61. package/src/mcp/transport/index.ts +294 -0
  62. package/src/mcp/transport/stdio.ts +324 -0
  63. package/src/mcp/transport/websocket.ts +484 -0
  64. package/src/mcp/types.ts +565 -0
  65. package/src/plugin-interface.ts +663 -0
  66. package/src/plugin-loader.ts +638 -0
  67. package/src/plugin-registry.ts +604 -0
  68. package/src/plugins/index.ts +34 -0
  69. package/src/plugins/official/hive-mind-plugin.ts +330 -0
  70. package/src/plugins/official/index.ts +24 -0
  71. package/src/plugins/official/maestro-plugin.ts +508 -0
  72. package/src/plugins/types.ts +108 -0
  73. package/src/resilience/bulkhead.ts +277 -0
  74. package/src/resilience/circuit-breaker.ts +326 -0
  75. package/src/resilience/index.ts +26 -0
  76. package/src/resilience/rate-limiter.ts +420 -0
  77. package/src/resilience/retry.ts +224 -0
  78. package/src/security/index.ts +39 -0
  79. package/src/security/input-validation.ts +265 -0
  80. package/src/security/secure-random.ts +159 -0
  81. package/src/services/index.ts +16 -0
  82. package/src/services/v3-progress.service.ts +505 -0
  83. package/src/types/agent.types.ts +144 -0
  84. package/src/types/index.ts +22 -0
  85. package/src/types/mcp.types.ts +300 -0
  86. package/src/types/memory.types.ts +263 -0
  87. package/src/types/swarm.types.ts +255 -0
  88. package/src/types/task.types.ts +205 -0
  89. package/src/types.ts +367 -0
  90. package/src/utils/secure-logger.d.ts +69 -0
  91. package/src/utils/secure-logger.d.ts.map +1 -0
  92. package/src/utils/secure-logger.js +208 -0
  93. package/src/utils/secure-logger.js.map +1 -0
  94. package/src/utils/secure-logger.ts +257 -0
  95. package/tmp.json +0 -0
  96. package/tsconfig.json +9 -0
@@ -0,0 +1,420 @@
1
+ /**
2
+ * Rate Limiter
3
+ *
4
+ * Production-ready rate limiting implementations.
5
+ *
6
+ * @module v3/shared/resilience/rate-limiter
7
+ */
8
+
9
+ /**
10
+ * Rate limiter options
11
+ */
12
+ export interface RateLimiterOptions {
13
+ /** Maximum requests allowed in the window */
14
+ maxRequests: number;
15
+
16
+ /** Time window in milliseconds */
17
+ windowMs: number;
18
+
19
+ /** Enable sliding window (vs fixed window) */
20
+ slidingWindow?: boolean;
21
+
22
+ /** Key generator for per-key limiting */
23
+ keyGenerator?: (context: unknown) => string;
24
+
25
+ /** Skip limiter for certain requests */
26
+ skip?: (context: unknown) => boolean;
27
+
28
+ /** Handler when rate limit is exceeded */
29
+ onRateLimited?: (key: string, remaining: number, resetAt: Date) => void;
30
+ }
31
+
32
+ /**
33
+ * Rate limit result
34
+ */
35
+ export interface RateLimitResult {
36
+ allowed: boolean;
37
+ remaining: number;
38
+ resetAt: Date;
39
+ retryAfter: number; // ms until reset
40
+ total: number;
41
+ used: number;
42
+ }
43
+
44
+ /**
45
+ * Base Rate Limiter interface
46
+ */
47
+ export interface RateLimiter {
48
+ /** Check if request is allowed */
49
+ check(key?: string): RateLimitResult;
50
+
51
+ /** Consume a request token */
52
+ consume(key?: string): RateLimitResult;
53
+
54
+ /** Reset a specific key or all keys */
55
+ reset(key?: string): void;
56
+
57
+ /** Get current status */
58
+ status(key?: string): RateLimitResult;
59
+ }
60
+
61
+ /**
62
+ * Request entry for tracking
63
+ */
64
+ interface RequestEntry {
65
+ timestamp: number;
66
+ key: string;
67
+ }
68
+
69
+ /**
70
+ * Sliding Window Rate Limiter
71
+ *
72
+ * Uses sliding window algorithm for smooth rate limiting.
73
+ *
74
+ * @example
75
+ * const limiter = new SlidingWindowRateLimiter({
76
+ * maxRequests: 100,
77
+ * windowMs: 60000, // 100 requests per minute
78
+ * });
79
+ *
80
+ * const result = limiter.consume('user-123');
81
+ * if (!result.allowed) {
82
+ * throw new Error(`Rate limited. Retry in ${result.retryAfter}ms`);
83
+ * }
84
+ */
85
+ export class SlidingWindowRateLimiter implements RateLimiter {
86
+ private readonly options: RateLimiterOptions;
87
+ private readonly requests: Map<string, RequestEntry[]> = new Map();
88
+ private cleanupInterval?: ReturnType<typeof setInterval>;
89
+
90
+ constructor(options: RateLimiterOptions) {
91
+ this.options = {
92
+ slidingWindow: true,
93
+ ...options,
94
+ };
95
+
96
+ // Periodic cleanup of old entries
97
+ this.cleanupInterval = setInterval(() => {
98
+ this.cleanup();
99
+ }, this.options.windowMs);
100
+ }
101
+
102
+ /**
103
+ * Check if a request would be allowed without consuming
104
+ */
105
+ check(key: string = 'default'): RateLimitResult {
106
+ this.cleanupKey(key);
107
+ const entries = this.requests.get(key) || [];
108
+
109
+ return {
110
+ allowed: entries.length < this.options.maxRequests,
111
+ remaining: Math.max(0, this.options.maxRequests - entries.length),
112
+ resetAt: this.getResetTime(entries),
113
+ retryAfter: this.getRetryAfter(entries),
114
+ total: this.options.maxRequests,
115
+ used: entries.length,
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Consume a request token
121
+ */
122
+ consume(key: string = 'default'): RateLimitResult {
123
+ // Clean old entries first
124
+ this.cleanupKey(key);
125
+
126
+ let entries = this.requests.get(key);
127
+ if (!entries) {
128
+ entries = [];
129
+ this.requests.set(key, entries);
130
+ }
131
+
132
+ // Check if allowed
133
+ if (entries.length >= this.options.maxRequests) {
134
+ const result: RateLimitResult = {
135
+ allowed: false,
136
+ remaining: 0,
137
+ resetAt: this.getResetTime(entries),
138
+ retryAfter: this.getRetryAfter(entries),
139
+ total: this.options.maxRequests,
140
+ used: entries.length,
141
+ };
142
+
143
+ this.options.onRateLimited?.(key, 0, result.resetAt);
144
+ return result;
145
+ }
146
+
147
+ // Add new entry
148
+ entries.push({ timestamp: Date.now(), key });
149
+
150
+ return {
151
+ allowed: true,
152
+ remaining: this.options.maxRequests - entries.length,
153
+ resetAt: this.getResetTime(entries),
154
+ retryAfter: 0,
155
+ total: this.options.maxRequests,
156
+ used: entries.length,
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Reset rate limit for a key
162
+ */
163
+ reset(key?: string): void {
164
+ if (key) {
165
+ this.requests.delete(key);
166
+ } else {
167
+ this.requests.clear();
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Get current status
173
+ */
174
+ status(key: string = 'default'): RateLimitResult {
175
+ return this.check(key);
176
+ }
177
+
178
+ /**
179
+ * Cleanup resources
180
+ */
181
+ destroy(): void {
182
+ if (this.cleanupInterval) {
183
+ clearInterval(this.cleanupInterval);
184
+ this.cleanupInterval = undefined;
185
+ }
186
+ this.requests.clear();
187
+ }
188
+
189
+ /**
190
+ * Clean old entries for a specific key
191
+ */
192
+ private cleanupKey(key: string): void {
193
+ const entries = this.requests.get(key);
194
+ if (!entries) return;
195
+
196
+ const cutoff = Date.now() - this.options.windowMs;
197
+ const filtered = entries.filter((e) => e.timestamp >= cutoff);
198
+
199
+ if (filtered.length === 0) {
200
+ this.requests.delete(key);
201
+ } else if (filtered.length !== entries.length) {
202
+ this.requests.set(key, filtered);
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Clean all old entries
208
+ */
209
+ private cleanup(): void {
210
+ for (const key of this.requests.keys()) {
211
+ this.cleanupKey(key);
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Get reset time based on oldest entry
217
+ */
218
+ private getResetTime(entries: RequestEntry[]): Date {
219
+ if (entries.length === 0) {
220
+ return new Date(Date.now() + this.options.windowMs);
221
+ }
222
+
223
+ const oldest = entries[0]!;
224
+ return new Date(oldest.timestamp + this.options.windowMs);
225
+ }
226
+
227
+ /**
228
+ * Get retry after time in ms
229
+ */
230
+ private getRetryAfter(entries: RequestEntry[]): number {
231
+ if (entries.length < this.options.maxRequests) {
232
+ return 0;
233
+ }
234
+
235
+ const oldest = entries[0]!;
236
+ const resetAt = oldest.timestamp + this.options.windowMs;
237
+ return Math.max(0, resetAt - Date.now());
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Token Bucket Rate Limiter
243
+ *
244
+ * Uses token bucket algorithm for burst-friendly rate limiting.
245
+ *
246
+ * @example
247
+ * const limiter = new TokenBucketRateLimiter({
248
+ * maxRequests: 10, // bucket size
249
+ * windowMs: 1000, // refill interval
250
+ * });
251
+ */
252
+ export class TokenBucketRateLimiter implements RateLimiter {
253
+ private readonly options: RateLimiterOptions;
254
+ private readonly buckets: Map<string, { tokens: number; lastRefill: number }> = new Map();
255
+ private cleanupInterval?: ReturnType<typeof setInterval>;
256
+
257
+ constructor(options: RateLimiterOptions) {
258
+ this.options = options;
259
+
260
+ // Periodic cleanup
261
+ this.cleanupInterval = setInterval(() => {
262
+ this.cleanup();
263
+ }, this.options.windowMs * 10);
264
+ }
265
+
266
+ /**
267
+ * Check if a request would be allowed
268
+ */
269
+ check(key: string = 'default'): RateLimitResult {
270
+ this.refill(key);
271
+ const bucket = this.getBucket(key);
272
+
273
+ return {
274
+ allowed: bucket.tokens >= 1,
275
+ remaining: Math.floor(bucket.tokens),
276
+ resetAt: new Date(bucket.lastRefill + this.options.windowMs),
277
+ retryAfter: bucket.tokens >= 1 ? 0 : this.options.windowMs,
278
+ total: this.options.maxRequests,
279
+ used: this.options.maxRequests - Math.floor(bucket.tokens),
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Consume a token
285
+ */
286
+ consume(key: string = 'default'): RateLimitResult {
287
+ this.refill(key);
288
+ const bucket = this.getBucket(key);
289
+
290
+ if (bucket.tokens < 1) {
291
+ const result: RateLimitResult = {
292
+ allowed: false,
293
+ remaining: 0,
294
+ resetAt: new Date(bucket.lastRefill + this.options.windowMs),
295
+ retryAfter: this.options.windowMs,
296
+ total: this.options.maxRequests,
297
+ used: this.options.maxRequests,
298
+ };
299
+
300
+ this.options.onRateLimited?.(key, 0, result.resetAt);
301
+ return result;
302
+ }
303
+
304
+ bucket.tokens -= 1;
305
+
306
+ return {
307
+ allowed: true,
308
+ remaining: Math.floor(bucket.tokens),
309
+ resetAt: new Date(bucket.lastRefill + this.options.windowMs),
310
+ retryAfter: 0,
311
+ total: this.options.maxRequests,
312
+ used: this.options.maxRequests - Math.floor(bucket.tokens),
313
+ };
314
+ }
315
+
316
+ /**
317
+ * Reset bucket for a key
318
+ */
319
+ reset(key?: string): void {
320
+ if (key) {
321
+ this.buckets.delete(key);
322
+ } else {
323
+ this.buckets.clear();
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Get current status
329
+ */
330
+ status(key: string = 'default'): RateLimitResult {
331
+ return this.check(key);
332
+ }
333
+
334
+ /**
335
+ * Cleanup resources
336
+ */
337
+ destroy(): void {
338
+ if (this.cleanupInterval) {
339
+ clearInterval(this.cleanupInterval);
340
+ this.cleanupInterval = undefined;
341
+ }
342
+ this.buckets.clear();
343
+ }
344
+
345
+ /**
346
+ * Get or create bucket for key
347
+ */
348
+ private getBucket(key: string): { tokens: number; lastRefill: number } {
349
+ let bucket = this.buckets.get(key);
350
+ if (!bucket) {
351
+ bucket = { tokens: this.options.maxRequests, lastRefill: Date.now() };
352
+ this.buckets.set(key, bucket);
353
+ }
354
+ return bucket;
355
+ }
356
+
357
+ /**
358
+ * Refill tokens based on elapsed time
359
+ */
360
+ private refill(key: string): void {
361
+ const bucket = this.getBucket(key);
362
+ const now = Date.now();
363
+ const elapsed = now - bucket.lastRefill;
364
+
365
+ if (elapsed >= this.options.windowMs) {
366
+ // Full refill after window
367
+ const intervals = Math.floor(elapsed / this.options.windowMs);
368
+ bucket.tokens = Math.min(
369
+ this.options.maxRequests,
370
+ bucket.tokens + intervals * this.options.maxRequests
371
+ );
372
+ bucket.lastRefill = now;
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Clean inactive buckets
378
+ */
379
+ private cleanup(): void {
380
+ const cutoff = Date.now() - this.options.windowMs * 10;
381
+
382
+ for (const [key, bucket] of this.buckets) {
383
+ if (bucket.lastRefill < cutoff && bucket.tokens >= this.options.maxRequests) {
384
+ this.buckets.delete(key);
385
+ }
386
+ }
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Rate limiter middleware for Express-like frameworks
392
+ */
393
+ export function createRateLimiterMiddleware(limiter: RateLimiter) {
394
+ return (req: { ip?: string; headers?: Record<string, string> }, res: {
395
+ status: (code: number) => { json: (body: unknown) => void };
396
+ setHeader: (name: string, value: string) => void;
397
+ }, next: () => void): void => {
398
+ // Get key from IP or header
399
+ const key = req.ip || req.headers?.['x-forwarded-for'] || 'anonymous';
400
+
401
+ const result = limiter.consume(key);
402
+
403
+ // Set rate limit headers
404
+ res.setHeader('X-RateLimit-Limit', String(result.total));
405
+ res.setHeader('X-RateLimit-Remaining', String(result.remaining));
406
+ res.setHeader('X-RateLimit-Reset', String(Math.ceil(result.resetAt.getTime() / 1000)));
407
+
408
+ if (!result.allowed) {
409
+ res.setHeader('Retry-After', String(Math.ceil(result.retryAfter / 1000)));
410
+ res.status(429).json({
411
+ error: 'Too Many Requests',
412
+ retryAfter: result.retryAfter,
413
+ resetAt: result.resetAt.toISOString(),
414
+ });
415
+ return;
416
+ }
417
+
418
+ next();
419
+ };
420
+ }
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Retry with Exponential Backoff
3
+ *
4
+ * Production-ready retry logic with jitter, max retries, and error filtering.
5
+ *
6
+ * @module v3/shared/resilience/retry
7
+ */
8
+
9
+ /**
10
+ * Retry options
11
+ */
12
+ export interface RetryOptions {
13
+ /** Maximum number of retry attempts (default: 3) */
14
+ maxAttempts: number;
15
+
16
+ /** Initial delay in milliseconds (default: 100) */
17
+ initialDelay: number;
18
+
19
+ /** Maximum delay in milliseconds (default: 10000) */
20
+ maxDelay: number;
21
+
22
+ /** Backoff multiplier (default: 2) */
23
+ backoffMultiplier: number;
24
+
25
+ /** Jitter factor 0-1 to randomize delays (default: 0.1) */
26
+ jitter: number;
27
+
28
+ /** Timeout for each attempt in milliseconds (default: 30000) */
29
+ timeout: number;
30
+
31
+ /** Errors that should trigger a retry (default: all errors) */
32
+ retryableErrors?: (error: Error) => boolean;
33
+
34
+ /** Callback for each retry attempt */
35
+ onRetry?: (error: Error, attempt: number, delay: number) => void;
36
+ }
37
+
38
+ /**
39
+ * Retry result
40
+ */
41
+ export interface RetryResult<T> {
42
+ success: boolean;
43
+ result?: T;
44
+ attempts: number;
45
+ totalTime: number;
46
+ errors: Error[];
47
+ }
48
+
49
+ /**
50
+ * Retry error with attempt history
51
+ */
52
+ export class RetryError extends Error {
53
+ constructor(
54
+ message: string,
55
+ public readonly attempts: number,
56
+ public readonly errors: Error[],
57
+ public readonly totalTime: number
58
+ ) {
59
+ super(message);
60
+ this.name = 'RetryError';
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Default retry options
66
+ */
67
+ const DEFAULT_OPTIONS: RetryOptions = {
68
+ maxAttempts: 3,
69
+ initialDelay: 100,
70
+ maxDelay: 10000,
71
+ backoffMultiplier: 2,
72
+ jitter: 0.1,
73
+ timeout: 30000,
74
+ };
75
+
76
+ /**
77
+ * Retry a function with exponential backoff
78
+ *
79
+ * @param fn Function to retry
80
+ * @param options Retry configuration
81
+ * @returns Result with success/failure and metadata
82
+ *
83
+ * @example
84
+ * const result = await retry(
85
+ * () => fetchData(),
86
+ * { maxAttempts: 5, initialDelay: 200 }
87
+ * );
88
+ *
89
+ * if (result.success) {
90
+ * console.log('Data:', result.result);
91
+ * } else {
92
+ * console.log('Failed after', result.attempts, 'attempts');
93
+ * }
94
+ */
95
+ export async function retry<T>(
96
+ fn: () => Promise<T>,
97
+ options: Partial<RetryOptions> = {}
98
+ ): Promise<RetryResult<T>> {
99
+ const opts = { ...DEFAULT_OPTIONS, ...options };
100
+ const errors: Error[] = [];
101
+ const startTime = Date.now();
102
+
103
+ for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
104
+ try {
105
+ // Execute with timeout
106
+ const result = await withTimeout(fn(), opts.timeout, attempt);
107
+
108
+ return {
109
+ success: true,
110
+ result,
111
+ attempts: attempt,
112
+ totalTime: Date.now() - startTime,
113
+ errors,
114
+ };
115
+ } catch (error) {
116
+ const err = error instanceof Error ? error : new Error(String(error));
117
+ errors.push(err);
118
+
119
+ // Check if error is retryable
120
+ if (opts.retryableErrors && !opts.retryableErrors(err)) {
121
+ return {
122
+ success: false,
123
+ attempts: attempt,
124
+ totalTime: Date.now() - startTime,
125
+ errors,
126
+ };
127
+ }
128
+
129
+ // If this was the last attempt, don't delay
130
+ if (attempt >= opts.maxAttempts) {
131
+ break;
132
+ }
133
+
134
+ // Calculate delay with exponential backoff and jitter
135
+ const baseDelay = opts.initialDelay * Math.pow(opts.backoffMultiplier, attempt - 1);
136
+ const jitter = baseDelay * opts.jitter * (Math.random() * 2 - 1);
137
+ const delay = Math.min(baseDelay + jitter, opts.maxDelay);
138
+
139
+ // Callback before retry
140
+ if (opts.onRetry) {
141
+ opts.onRetry(err, attempt, delay);
142
+ }
143
+
144
+ // Wait before next attempt
145
+ await sleep(delay);
146
+ }
147
+ }
148
+
149
+ return {
150
+ success: false,
151
+ attempts: opts.maxAttempts,
152
+ totalTime: Date.now() - startTime,
153
+ errors,
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Wrap a function with retry behavior
159
+ *
160
+ * @param fn Function to wrap
161
+ * @param options Retry configuration
162
+ * @returns Wrapped function that retries on failure
163
+ */
164
+ export function withRetry<T extends (...args: unknown[]) => Promise<unknown>>(
165
+ fn: T,
166
+ options: Partial<RetryOptions> = {}
167
+ ): (...args: Parameters<T>) => Promise<RetryResult<Awaited<ReturnType<T>>>> {
168
+ return async (...args: Parameters<T>) => {
169
+ return retry(() => fn(...args) as Promise<Awaited<ReturnType<T>>>, options);
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Execute with timeout
175
+ */
176
+ async function withTimeout<T>(promise: Promise<T>, timeout: number, attempt: number): Promise<T> {
177
+ const timeoutPromise = new Promise<never>((_, reject) => {
178
+ setTimeout(() => {
179
+ reject(new Error(`Attempt ${attempt} timed out after ${timeout}ms`));
180
+ }, timeout);
181
+ });
182
+
183
+ return Promise.race([promise, timeoutPromise]);
184
+ }
185
+
186
+ /**
187
+ * Sleep for specified milliseconds
188
+ */
189
+ function sleep(ms: number): Promise<void> {
190
+ return new Promise((resolve) => setTimeout(resolve, ms));
191
+ }
192
+
193
+ /**
194
+ * Common retryable error predicates
195
+ */
196
+ export const RetryableErrors = {
197
+ /** Network errors (ECONNRESET, ETIMEDOUT, etc.) */
198
+ network: (error: Error): boolean => {
199
+ const networkCodes = ['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED', 'ENOTFOUND', 'EAI_AGAIN'];
200
+ return networkCodes.some((code) => error.message.includes(code));
201
+ },
202
+
203
+ /** Rate limit errors (429) */
204
+ rateLimit: (error: Error): boolean => {
205
+ return error.message.includes('429') || error.message.toLowerCase().includes('rate limit');
206
+ },
207
+
208
+ /** Server errors (5xx) */
209
+ serverError: (error: Error): boolean => {
210
+ return /5\d\d/.test(error.message) || error.message.includes('Internal Server Error');
211
+ },
212
+
213
+ /** Transient errors (network + rate limit + 5xx) */
214
+ transient: (error: Error): boolean => {
215
+ return (
216
+ RetryableErrors.network(error) ||
217
+ RetryableErrors.rateLimit(error) ||
218
+ RetryableErrors.serverError(error)
219
+ );
220
+ },
221
+
222
+ /** All errors are retryable */
223
+ all: (): boolean => true,
224
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Security Module
3
+ *
4
+ * Shared security utilities for V3 Claude Flow.
5
+ *
6
+ * @module v3/shared/security
7
+ */
8
+
9
+ // Secure random generation
10
+ export {
11
+ generateSecureId,
12
+ generateUUID,
13
+ generateSecureToken,
14
+ generateShortId,
15
+ generateSessionId,
16
+ generateAgentId,
17
+ generateTaskId,
18
+ generateMemoryId,
19
+ generateEventId,
20
+ generateSwarmId,
21
+ generatePatternId,
22
+ generateTrajectoryId,
23
+ secureRandomInt,
24
+ secureRandomChoice,
25
+ secureShuffleArray,
26
+ } from './secure-random.js';
27
+
28
+ // Input validation
29
+ export {
30
+ validateInput,
31
+ sanitizeString,
32
+ validatePath,
33
+ validateCommand,
34
+ validateTags,
35
+ isValidIdentifier,
36
+ escapeForSql,
37
+ type ValidationResult,
38
+ type ValidationOptions,
39
+ } from './input-validation.js';