@litmers/cursorflow-orchestrator 0.1.20 → 0.1.26

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 (224) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/commands/cursorflow-clean.md +19 -0
  3. package/commands/cursorflow-runs.md +59 -0
  4. package/commands/cursorflow-stop.md +55 -0
  5. package/dist/cli/clean.js +171 -0
  6. package/dist/cli/clean.js.map +1 -1
  7. package/dist/cli/index.js +7 -0
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/cli/init.js +1 -1
  10. package/dist/cli/init.js.map +1 -1
  11. package/dist/cli/logs.js +83 -42
  12. package/dist/cli/logs.js.map +1 -1
  13. package/dist/cli/monitor.d.ts +7 -0
  14. package/dist/cli/monitor.js +1007 -189
  15. package/dist/cli/monitor.js.map +1 -1
  16. package/dist/cli/prepare.js +4 -3
  17. package/dist/cli/prepare.js.map +1 -1
  18. package/dist/cli/resume.js +188 -236
  19. package/dist/cli/resume.js.map +1 -1
  20. package/dist/cli/run.js +8 -3
  21. package/dist/cli/run.js.map +1 -1
  22. package/dist/cli/runs.d.ts +5 -0
  23. package/dist/cli/runs.js +214 -0
  24. package/dist/cli/runs.js.map +1 -0
  25. package/dist/cli/setup-commands.js +0 -0
  26. package/dist/cli/signal.js +1 -1
  27. package/dist/cli/signal.js.map +1 -1
  28. package/dist/cli/stop.d.ts +5 -0
  29. package/dist/cli/stop.js +215 -0
  30. package/dist/cli/stop.js.map +1 -0
  31. package/dist/cli/tasks.d.ts +10 -0
  32. package/dist/cli/tasks.js +165 -0
  33. package/dist/cli/tasks.js.map +1 -0
  34. package/dist/core/auto-recovery.d.ts +212 -0
  35. package/dist/core/auto-recovery.js +737 -0
  36. package/dist/core/auto-recovery.js.map +1 -0
  37. package/dist/core/failure-policy.d.ts +156 -0
  38. package/dist/core/failure-policy.js +488 -0
  39. package/dist/core/failure-policy.js.map +1 -0
  40. package/dist/core/orchestrator.d.ts +15 -2
  41. package/dist/core/orchestrator.js +392 -15
  42. package/dist/core/orchestrator.js.map +1 -1
  43. package/dist/core/reviewer.d.ts +2 -0
  44. package/dist/core/reviewer.js +2 -0
  45. package/dist/core/reviewer.js.map +1 -1
  46. package/dist/core/runner.d.ts +33 -10
  47. package/dist/core/runner.js +321 -146
  48. package/dist/core/runner.js.map +1 -1
  49. package/dist/services/logging/buffer.d.ts +67 -0
  50. package/dist/services/logging/buffer.js +309 -0
  51. package/dist/services/logging/buffer.js.map +1 -0
  52. package/dist/services/logging/console.d.ts +89 -0
  53. package/dist/services/logging/console.js +169 -0
  54. package/dist/services/logging/console.js.map +1 -0
  55. package/dist/services/logging/file-writer.d.ts +71 -0
  56. package/dist/services/logging/file-writer.js +516 -0
  57. package/dist/services/logging/file-writer.js.map +1 -0
  58. package/dist/services/logging/formatter.d.ts +39 -0
  59. package/dist/services/logging/formatter.js +227 -0
  60. package/dist/services/logging/formatter.js.map +1 -0
  61. package/dist/services/logging/index.d.ts +11 -0
  62. package/dist/services/logging/index.js +30 -0
  63. package/dist/services/logging/index.js.map +1 -0
  64. package/dist/services/logging/parser.d.ts +31 -0
  65. package/dist/services/logging/parser.js +222 -0
  66. package/dist/services/logging/parser.js.map +1 -0
  67. package/dist/services/process/index.d.ts +59 -0
  68. package/dist/services/process/index.js +257 -0
  69. package/dist/services/process/index.js.map +1 -0
  70. package/dist/types/agent.d.ts +20 -0
  71. package/dist/types/agent.js +6 -0
  72. package/dist/types/agent.js.map +1 -0
  73. package/dist/types/config.d.ts +65 -0
  74. package/dist/types/config.js +6 -0
  75. package/dist/types/config.js.map +1 -0
  76. package/dist/types/events.d.ts +125 -0
  77. package/dist/types/events.js +6 -0
  78. package/dist/types/events.js.map +1 -0
  79. package/dist/types/index.d.ts +12 -0
  80. package/dist/types/index.js +37 -0
  81. package/dist/types/index.js.map +1 -0
  82. package/dist/types/lane.d.ts +43 -0
  83. package/dist/types/lane.js +6 -0
  84. package/dist/types/lane.js.map +1 -0
  85. package/dist/types/logging.d.ts +71 -0
  86. package/dist/types/logging.js +16 -0
  87. package/dist/types/logging.js.map +1 -0
  88. package/dist/types/review.d.ts +17 -0
  89. package/dist/types/review.js +6 -0
  90. package/dist/types/review.js.map +1 -0
  91. package/dist/types/run.d.ts +32 -0
  92. package/dist/types/run.js +6 -0
  93. package/dist/types/run.js.map +1 -0
  94. package/dist/types/task.d.ts +71 -0
  95. package/dist/types/task.js +6 -0
  96. package/dist/types/task.js.map +1 -0
  97. package/dist/ui/components.d.ts +134 -0
  98. package/dist/ui/components.js +389 -0
  99. package/dist/ui/components.js.map +1 -0
  100. package/dist/ui/log-viewer.d.ts +49 -0
  101. package/dist/ui/log-viewer.js +449 -0
  102. package/dist/ui/log-viewer.js.map +1 -0
  103. package/dist/utils/checkpoint.d.ts +87 -0
  104. package/dist/utils/checkpoint.js +317 -0
  105. package/dist/utils/checkpoint.js.map +1 -0
  106. package/dist/utils/config.d.ts +4 -0
  107. package/dist/utils/config.js +11 -2
  108. package/dist/utils/config.js.map +1 -1
  109. package/dist/utils/cursor-agent.js.map +1 -1
  110. package/dist/utils/dependency.d.ts +74 -0
  111. package/dist/utils/dependency.js +420 -0
  112. package/dist/utils/dependency.js.map +1 -0
  113. package/dist/utils/doctor.js +10 -5
  114. package/dist/utils/doctor.js.map +1 -1
  115. package/dist/utils/enhanced-logger.d.ts +10 -33
  116. package/dist/utils/enhanced-logger.js +94 -9
  117. package/dist/utils/enhanced-logger.js.map +1 -1
  118. package/dist/utils/git.d.ts +121 -0
  119. package/dist/utils/git.js +322 -2
  120. package/dist/utils/git.js.map +1 -1
  121. package/dist/utils/health.d.ts +91 -0
  122. package/dist/utils/health.js +556 -0
  123. package/dist/utils/health.js.map +1 -0
  124. package/dist/utils/lock.d.ts +95 -0
  125. package/dist/utils/lock.js +332 -0
  126. package/dist/utils/lock.js.map +1 -0
  127. package/dist/utils/log-buffer.d.ts +17 -0
  128. package/dist/utils/log-buffer.js +14 -0
  129. package/dist/utils/log-buffer.js.map +1 -0
  130. package/dist/utils/log-constants.d.ts +23 -0
  131. package/dist/utils/log-constants.js +28 -0
  132. package/dist/utils/log-constants.js.map +1 -0
  133. package/dist/utils/log-formatter.d.ts +9 -0
  134. package/dist/utils/log-formatter.js +113 -70
  135. package/dist/utils/log-formatter.js.map +1 -1
  136. package/dist/utils/log-service.d.ts +19 -0
  137. package/dist/utils/log-service.js +47 -0
  138. package/dist/utils/log-service.js.map +1 -0
  139. package/dist/utils/logger.d.ts +46 -27
  140. package/dist/utils/logger.js +82 -60
  141. package/dist/utils/logger.js.map +1 -1
  142. package/dist/utils/process-manager.d.ts +21 -0
  143. package/dist/utils/process-manager.js +138 -0
  144. package/dist/utils/process-manager.js.map +1 -0
  145. package/dist/utils/retry.d.ts +121 -0
  146. package/dist/utils/retry.js +374 -0
  147. package/dist/utils/retry.js.map +1 -0
  148. package/dist/utils/run-service.d.ts +88 -0
  149. package/dist/utils/run-service.js +412 -0
  150. package/dist/utils/run-service.js.map +1 -0
  151. package/dist/utils/state.d.ts +58 -2
  152. package/dist/utils/state.js +306 -3
  153. package/dist/utils/state.js.map +1 -1
  154. package/dist/utils/task-service.d.ts +82 -0
  155. package/dist/utils/task-service.js +348 -0
  156. package/dist/utils/task-service.js.map +1 -0
  157. package/dist/utils/types.d.ts +2 -272
  158. package/dist/utils/types.js +16 -0
  159. package/dist/utils/types.js.map +1 -1
  160. package/package.json +38 -23
  161. package/scripts/ai-security-check.js +0 -1
  162. package/scripts/local-security-gate.sh +0 -0
  163. package/scripts/monitor-lanes.sh +94 -0
  164. package/scripts/patches/test-cursor-agent.js +0 -1
  165. package/scripts/release.sh +0 -0
  166. package/scripts/setup-security.sh +0 -0
  167. package/scripts/stream-logs.sh +72 -0
  168. package/scripts/verify-and-fix.sh +0 -0
  169. package/src/cli/clean.ts +180 -0
  170. package/src/cli/index.ts +7 -0
  171. package/src/cli/init.ts +1 -1
  172. package/src/cli/logs.ts +79 -42
  173. package/src/cli/monitor.ts +1815 -899
  174. package/src/cli/prepare.ts +4 -3
  175. package/src/cli/resume.ts +220 -277
  176. package/src/cli/run.ts +9 -3
  177. package/src/cli/runs.ts +212 -0
  178. package/src/cli/setup-commands.ts +0 -0
  179. package/src/cli/signal.ts +1 -1
  180. package/src/cli/stop.ts +209 -0
  181. package/src/cli/tasks.ts +154 -0
  182. package/src/core/auto-recovery.ts +909 -0
  183. package/src/core/failure-policy.ts +592 -0
  184. package/src/core/orchestrator.ts +1131 -675
  185. package/src/core/reviewer.ts +4 -0
  186. package/src/core/runner.ts +388 -162
  187. package/src/services/logging/buffer.ts +326 -0
  188. package/src/services/logging/console.ts +193 -0
  189. package/src/services/logging/file-writer.ts +526 -0
  190. package/src/services/logging/formatter.ts +268 -0
  191. package/src/services/logging/index.ts +16 -0
  192. package/src/services/logging/parser.ts +232 -0
  193. package/src/services/process/index.ts +261 -0
  194. package/src/types/agent.ts +24 -0
  195. package/src/types/config.ts +79 -0
  196. package/src/types/events.ts +156 -0
  197. package/src/types/index.ts +29 -0
  198. package/src/types/lane.ts +56 -0
  199. package/src/types/logging.ts +96 -0
  200. package/src/types/review.ts +20 -0
  201. package/src/types/run.ts +37 -0
  202. package/src/types/task.ts +79 -0
  203. package/src/ui/components.ts +430 -0
  204. package/src/ui/log-viewer.ts +485 -0
  205. package/src/utils/checkpoint.ts +374 -0
  206. package/src/utils/config.ts +11 -2
  207. package/src/utils/cursor-agent.ts +1 -1
  208. package/src/utils/dependency.ts +482 -0
  209. package/src/utils/doctor.ts +11 -5
  210. package/src/utils/enhanced-logger.ts +108 -49
  211. package/src/utils/git.ts +374 -2
  212. package/src/utils/health.ts +596 -0
  213. package/src/utils/lock.ts +346 -0
  214. package/src/utils/log-buffer.ts +28 -0
  215. package/src/utils/log-constants.ts +26 -0
  216. package/src/utils/log-formatter.ts +120 -37
  217. package/src/utils/log-service.ts +49 -0
  218. package/src/utils/logger.ts +100 -51
  219. package/src/utils/process-manager.ts +100 -0
  220. package/src/utils/retry.ts +413 -0
  221. package/src/utils/run-service.ts +433 -0
  222. package/src/utils/state.ts +369 -3
  223. package/src/utils/task-service.ts +370 -0
  224. package/src/utils/types.ts +2 -315
@@ -0,0 +1,413 @@
1
+ /**
2
+ * Enhanced retry logic with exponential backoff and circuit breaker
3
+ */
4
+
5
+ import * as logger from './logger';
6
+
7
+ export interface RetryConfig {
8
+ /** Maximum number of retries */
9
+ maxRetries: number;
10
+ /** Base delay in milliseconds */
11
+ baseDelayMs: number;
12
+ /** Maximum delay in milliseconds */
13
+ maxDelayMs: number;
14
+ /** Backoff multiplier */
15
+ backoffFactor: number;
16
+ /** Add random jitter to delays */
17
+ jitter: boolean;
18
+ /** Timeout for each attempt in milliseconds */
19
+ attemptTimeoutMs?: number;
20
+ }
21
+
22
+ export const DEFAULT_RETRY_CONFIG: RetryConfig = {
23
+ maxRetries: 3,
24
+ baseDelayMs: 1000,
25
+ maxDelayMs: 30000,
26
+ backoffFactor: 2,
27
+ jitter: true,
28
+ attemptTimeoutMs: undefined,
29
+ };
30
+
31
+ export interface CircuitBreakerConfig {
32
+ /** Number of consecutive failures before opening circuit */
33
+ failureThreshold: number;
34
+ /** Time in milliseconds before attempting to close circuit */
35
+ resetTimeoutMs: number;
36
+ /** Number of successful calls needed to close circuit */
37
+ successThreshold: number;
38
+ }
39
+
40
+ export const DEFAULT_CIRCUIT_BREAKER_CONFIG: CircuitBreakerConfig = {
41
+ failureThreshold: 5,
42
+ resetTimeoutMs: 60000,
43
+ successThreshold: 2,
44
+ };
45
+
46
+ export enum CircuitState {
47
+ CLOSED = 'CLOSED', // Normal operation
48
+ OPEN = 'OPEN', // Blocking calls
49
+ HALF_OPEN = 'HALF_OPEN', // Testing if service recovered
50
+ }
51
+
52
+ /**
53
+ * Circuit breaker implementation for managing service availability
54
+ */
55
+ export class CircuitBreaker {
56
+ private state: CircuitState = CircuitState.CLOSED;
57
+ private failureCount: number = 0;
58
+ private successCount: number = 0;
59
+ private lastFailureTime: number = 0;
60
+ private readonly config: CircuitBreakerConfig;
61
+ private readonly name: string;
62
+
63
+ constructor(name: string, config: Partial<CircuitBreakerConfig> = {}) {
64
+ this.name = name;
65
+ this.config = { ...DEFAULT_CIRCUIT_BREAKER_CONFIG, ...config };
66
+ }
67
+
68
+ /**
69
+ * Check if circuit allows a call
70
+ */
71
+ canCall(): boolean {
72
+ if (this.state === CircuitState.CLOSED) {
73
+ return true;
74
+ }
75
+
76
+ if (this.state === CircuitState.OPEN) {
77
+ // Check if enough time has passed to try again
78
+ if (Date.now() - this.lastFailureTime >= this.config.resetTimeoutMs) {
79
+ this.state = CircuitState.HALF_OPEN;
80
+ this.successCount = 0;
81
+ logger.info(`[CircuitBreaker:${this.name}] Transitioning to HALF_OPEN`);
82
+ return true;
83
+ }
84
+ return false;
85
+ }
86
+
87
+ // HALF_OPEN - allow limited calls
88
+ return true;
89
+ }
90
+
91
+ /**
92
+ * Record a successful call
93
+ */
94
+ recordSuccess(): void {
95
+ if (this.state === CircuitState.HALF_OPEN) {
96
+ this.successCount++;
97
+ if (this.successCount >= this.config.successThreshold) {
98
+ this.state = CircuitState.CLOSED;
99
+ this.failureCount = 0;
100
+ logger.info(`[CircuitBreaker:${this.name}] Circuit CLOSED after recovery`);
101
+ }
102
+ } else if (this.state === CircuitState.CLOSED) {
103
+ // Reset failure count on success
104
+ this.failureCount = Math.max(0, this.failureCount - 1);
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Record a failed call
110
+ */
111
+ recordFailure(): void {
112
+ this.failureCount++;
113
+ this.lastFailureTime = Date.now();
114
+
115
+ if (this.state === CircuitState.HALF_OPEN) {
116
+ // Immediately open circuit on failure in half-open state
117
+ this.state = CircuitState.OPEN;
118
+ logger.warn(`[CircuitBreaker:${this.name}] Circuit OPEN (failure in half-open state)`);
119
+ } else if (this.state === CircuitState.CLOSED && this.failureCount >= this.config.failureThreshold) {
120
+ this.state = CircuitState.OPEN;
121
+ logger.warn(`[CircuitBreaker:${this.name}] Circuit OPEN (threshold reached: ${this.failureCount} failures)`);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Get current circuit state
127
+ */
128
+ getState(): CircuitState {
129
+ return this.state;
130
+ }
131
+
132
+ /**
133
+ * Get time until circuit might close (for OPEN state)
134
+ */
135
+ getTimeUntilRetry(): number {
136
+ if (this.state !== CircuitState.OPEN) {
137
+ return 0;
138
+ }
139
+ return Math.max(0, this.config.resetTimeoutMs - (Date.now() - this.lastFailureTime));
140
+ }
141
+
142
+ /**
143
+ * Force reset the circuit breaker
144
+ */
145
+ reset(): void {
146
+ this.state = CircuitState.CLOSED;
147
+ this.failureCount = 0;
148
+ this.successCount = 0;
149
+ this.lastFailureTime = 0;
150
+ }
151
+
152
+ /**
153
+ * Get circuit breaker status for monitoring
154
+ */
155
+ getStatus(): { state: CircuitState; failureCount: number; timeUntilRetry: number } {
156
+ return {
157
+ state: this.state,
158
+ failureCount: this.failureCount,
159
+ timeUntilRetry: this.getTimeUntilRetry(),
160
+ };
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Global circuit breaker registry
166
+ */
167
+ const circuitBreakers = new Map<string, CircuitBreaker>();
168
+
169
+ /**
170
+ * Get or create a circuit breaker by name
171
+ */
172
+ export function getCircuitBreaker(name: string, config?: Partial<CircuitBreakerConfig>): CircuitBreaker {
173
+ let breaker = circuitBreakers.get(name);
174
+ if (!breaker) {
175
+ breaker = new CircuitBreaker(name, config);
176
+ circuitBreakers.set(name, breaker);
177
+ }
178
+ return breaker;
179
+ }
180
+
181
+ /**
182
+ * Calculate delay with exponential backoff and optional jitter
183
+ */
184
+ export function calculateDelay(attempt: number, config: RetryConfig): number {
185
+ let delay = config.baseDelayMs * Math.pow(config.backoffFactor, attempt);
186
+ delay = Math.min(delay, config.maxDelayMs);
187
+
188
+ if (config.jitter) {
189
+ // Add random jitter of ±25%
190
+ const jitterRange = delay * 0.25;
191
+ delay = delay - jitterRange + Math.random() * jitterRange * 2;
192
+ }
193
+
194
+ return Math.round(delay);
195
+ }
196
+
197
+ /**
198
+ * Sleep for specified milliseconds
199
+ */
200
+ export function sleep(ms: number): Promise<void> {
201
+ return new Promise(resolve => setTimeout(resolve, ms));
202
+ }
203
+
204
+ /**
205
+ * Wrap a promise with timeout
206
+ */
207
+ export function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message?: string): Promise<T> {
208
+ return new Promise((resolve, reject) => {
209
+ const timer = setTimeout(() => {
210
+ reject(new Error(message || `Operation timed out after ${timeoutMs}ms`));
211
+ }, timeoutMs);
212
+
213
+ promise
214
+ .then(result => {
215
+ clearTimeout(timer);
216
+ resolve(result);
217
+ })
218
+ .catch(error => {
219
+ clearTimeout(timer);
220
+ reject(error);
221
+ });
222
+ });
223
+ }
224
+
225
+ export interface RetryResult<T> {
226
+ success: boolean;
227
+ result?: T;
228
+ error?: Error;
229
+ attempts: number;
230
+ totalTimeMs: number;
231
+ }
232
+
233
+ /**
234
+ * Execute a function with enhanced retry logic
235
+ */
236
+ export async function withEnhancedRetry<T>(
237
+ fn: () => Promise<T>,
238
+ options: {
239
+ config?: Partial<RetryConfig>;
240
+ circuitBreaker?: CircuitBreaker | string;
241
+ shouldRetry?: (error: Error, attempt: number) => boolean;
242
+ onRetry?: (error: Error, attempt: number, delayMs: number) => void;
243
+ label?: string;
244
+ } = {}
245
+ ): Promise<RetryResult<T>> {
246
+ const config = { ...DEFAULT_RETRY_CONFIG, ...options.config };
247
+ const label = options.label || 'operation';
248
+ const startTime = Date.now();
249
+
250
+ // Get circuit breaker if specified
251
+ let breaker: CircuitBreaker | undefined;
252
+ if (options.circuitBreaker) {
253
+ breaker = typeof options.circuitBreaker === 'string'
254
+ ? getCircuitBreaker(options.circuitBreaker)
255
+ : options.circuitBreaker;
256
+ }
257
+
258
+ // Check circuit breaker before starting
259
+ if (breaker && !breaker.canCall()) {
260
+ const waitTime = breaker.getTimeUntilRetry();
261
+ return {
262
+ success: false,
263
+ error: new Error(`Circuit breaker is OPEN. Retry in ${Math.round(waitTime / 1000)}s`),
264
+ attempts: 0,
265
+ totalTimeMs: Date.now() - startTime,
266
+ };
267
+ }
268
+
269
+ let lastError: Error | undefined;
270
+
271
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
272
+ try {
273
+ // Execute with optional timeout
274
+ let result: T;
275
+ if (config.attemptTimeoutMs) {
276
+ result = await withTimeout(fn(), config.attemptTimeoutMs, `${label} timed out`);
277
+ } else {
278
+ result = await fn();
279
+ }
280
+
281
+ // Success - record in circuit breaker
282
+ if (breaker) {
283
+ breaker.recordSuccess();
284
+ }
285
+
286
+ return {
287
+ success: true,
288
+ result,
289
+ attempts: attempt + 1,
290
+ totalTimeMs: Date.now() - startTime,
291
+ };
292
+ } catch (error: any) {
293
+ lastError = error;
294
+
295
+ // Record failure in circuit breaker
296
+ if (breaker) {
297
+ breaker.recordFailure();
298
+ if (!breaker.canCall()) {
299
+ logger.warn(`[${label}] Circuit breaker opened, stopping retries`);
300
+ break;
301
+ }
302
+ }
303
+
304
+ // Check if we should retry
305
+ if (options.shouldRetry && !options.shouldRetry(error, attempt)) {
306
+ logger.warn(`[${label}] Error is not retryable: ${error.message}`);
307
+ break;
308
+ }
309
+
310
+ // Check if we have more retries
311
+ if (attempt >= config.maxRetries) {
312
+ logger.error(`[${label}] All ${config.maxRetries + 1} attempts failed`);
313
+ break;
314
+ }
315
+
316
+ // Calculate delay
317
+ const delay = calculateDelay(attempt, config);
318
+
319
+ // Notify on retry
320
+ if (options.onRetry) {
321
+ options.onRetry(error, attempt, delay);
322
+ } else {
323
+ logger.warn(`[${label}] Attempt ${attempt + 1} failed: ${error.message}. Retrying in ${delay}ms...`);
324
+ }
325
+
326
+ await sleep(delay);
327
+ }
328
+ }
329
+
330
+ return {
331
+ success: false,
332
+ error: lastError,
333
+ attempts: config.maxRetries + 1,
334
+ totalTimeMs: Date.now() - startTime,
335
+ };
336
+ }
337
+
338
+ /**
339
+ * Check if an error is transient and worth retrying
340
+ */
341
+ export function isTransientError(error: Error): boolean {
342
+ const message = error.message.toLowerCase();
343
+
344
+ // Network errors
345
+ if (message.includes('econnreset') ||
346
+ message.includes('econnrefused') ||
347
+ message.includes('etimedout') ||
348
+ message.includes('enotfound') ||
349
+ message.includes('socket hang up')) {
350
+ return true;
351
+ }
352
+
353
+ // Service unavailable
354
+ if (message.includes('unavailable') ||
355
+ message.includes('503') ||
356
+ message.includes('502') ||
357
+ message.includes('504')) {
358
+ return true;
359
+ }
360
+
361
+ // Rate limiting (should retry after delay)
362
+ if (message.includes('rate limit') ||
363
+ message.includes('429') ||
364
+ message.includes('too many requests')) {
365
+ return true;
366
+ }
367
+
368
+ // Temporary server errors
369
+ if (message.includes('internal server error') ||
370
+ message.includes('500') ||
371
+ message.includes('temporarily')) {
372
+ return true;
373
+ }
374
+
375
+ return false;
376
+ }
377
+
378
+ /**
379
+ * Check if an error is permanent and should not be retried
380
+ */
381
+ export function isPermanentError(error: Error): boolean {
382
+ const message = error.message.toLowerCase();
383
+
384
+ // Authentication errors
385
+ if (message.includes('unauthorized') ||
386
+ message.includes('401') ||
387
+ message.includes('forbidden') ||
388
+ message.includes('403') ||
389
+ message.includes('not authenticated')) {
390
+ return true;
391
+ }
392
+
393
+ // Configuration/setup errors
394
+ if (message.includes('not found') ||
395
+ message.includes('404') ||
396
+ message.includes('invalid configuration') ||
397
+ message.includes('invalid api key')) {
398
+ return true;
399
+ }
400
+
401
+ return false;
402
+ }
403
+
404
+ /**
405
+ * Default retry predicate
406
+ */
407
+ export function defaultShouldRetry(error: Error, attempt: number): boolean {
408
+ if (isPermanentError(error)) {
409
+ return false;
410
+ }
411
+ return isTransientError(error);
412
+ }
413
+