digital-workers 2.1.1 → 2.3.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 (197) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +136 -180
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +34 -21
  5. package/dist/actions.js.map +1 -1
  6. package/dist/agent-comms.d.ts +438 -0
  7. package/dist/agent-comms.d.ts.map +1 -0
  8. package/dist/agent-comms.js +677 -0
  9. package/dist/agent-comms.js.map +1 -0
  10. package/dist/approve.d.ts +40 -8
  11. package/dist/approve.d.ts.map +1 -1
  12. package/dist/approve.js +86 -20
  13. package/dist/approve.js.map +1 -1
  14. package/dist/ask.d.ts +38 -7
  15. package/dist/ask.d.ts.map +1 -1
  16. package/dist/ask.js +85 -25
  17. package/dist/ask.js.map +1 -1
  18. package/dist/browse.d.ts +223 -0
  19. package/dist/browse.d.ts.map +1 -0
  20. package/dist/browse.js +392 -0
  21. package/dist/browse.js.map +1 -0
  22. package/dist/capability-tiers.d.ts +230 -0
  23. package/dist/capability-tiers.d.ts.map +1 -0
  24. package/dist/capability-tiers.js +388 -0
  25. package/dist/capability-tiers.js.map +1 -0
  26. package/dist/cascade-context.d.ts +523 -0
  27. package/dist/cascade-context.d.ts.map +1 -0
  28. package/dist/cascade-context.js +494 -0
  29. package/dist/cascade-context.js.map +1 -0
  30. package/dist/client.d.ts +162 -0
  31. package/dist/client.d.ts.map +1 -0
  32. package/dist/client.js +64 -0
  33. package/dist/client.js.map +1 -0
  34. package/dist/decide.d.ts +42 -6
  35. package/dist/decide.d.ts.map +1 -1
  36. package/dist/decide.js +54 -11
  37. package/dist/decide.js.map +1 -1
  38. package/dist/do.d.ts +36 -7
  39. package/dist/do.d.ts.map +1 -1
  40. package/dist/do.js +82 -39
  41. package/dist/do.js.map +1 -1
  42. package/dist/error-escalation.d.ts +416 -0
  43. package/dist/error-escalation.d.ts.map +1 -0
  44. package/dist/error-escalation.js +656 -0
  45. package/dist/error-escalation.js.map +1 -0
  46. package/dist/generate.d.ts +48 -7
  47. package/dist/generate.d.ts.map +1 -1
  48. package/dist/generate.js +49 -8
  49. package/dist/generate.js.map +1 -1
  50. package/dist/goals.d.ts +10 -9
  51. package/dist/goals.d.ts.map +1 -1
  52. package/dist/goals.js +30 -24
  53. package/dist/goals.js.map +1 -1
  54. package/dist/image.d.ts +189 -0
  55. package/dist/image.d.ts.map +1 -0
  56. package/dist/image.js +528 -0
  57. package/dist/image.js.map +1 -0
  58. package/dist/index.d.ts +59 -2
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +92 -2
  61. package/dist/index.js.map +1 -1
  62. package/dist/is.d.ts +45 -10
  63. package/dist/is.d.ts.map +1 -1
  64. package/dist/is.js +56 -21
  65. package/dist/is.js.map +1 -1
  66. package/dist/kpis.d.ts +24 -15
  67. package/dist/kpis.d.ts.map +1 -1
  68. package/dist/kpis.js +16 -14
  69. package/dist/kpis.js.map +1 -1
  70. package/dist/load-balancing.d.ts +395 -0
  71. package/dist/load-balancing.d.ts.map +1 -0
  72. package/dist/load-balancing.js +991 -0
  73. package/dist/load-balancing.js.map +1 -0
  74. package/dist/logger.d.ts +76 -0
  75. package/dist/logger.d.ts.map +1 -0
  76. package/dist/logger.js +39 -0
  77. package/dist/logger.js.map +1 -0
  78. package/dist/notify.d.ts +38 -9
  79. package/dist/notify.d.ts.map +1 -1
  80. package/dist/notify.js +72 -17
  81. package/dist/notify.js.map +1 -1
  82. package/dist/role.d.ts +5 -4
  83. package/dist/role.d.ts.map +1 -1
  84. package/dist/role.js +13 -10
  85. package/dist/role.js.map +1 -1
  86. package/dist/runtime.d.ts +310 -0
  87. package/dist/runtime.d.ts.map +1 -0
  88. package/dist/runtime.js +510 -0
  89. package/dist/runtime.js.map +1 -0
  90. package/dist/team.d.ts +11 -6
  91. package/dist/team.d.ts.map +1 -1
  92. package/dist/team.js +22 -15
  93. package/dist/team.js.map +1 -1
  94. package/dist/transports/email.d.ts +318 -0
  95. package/dist/transports/email.d.ts.map +1 -0
  96. package/dist/transports/email.js +779 -0
  97. package/dist/transports/email.js.map +1 -0
  98. package/dist/transports/slack.d.ts +515 -0
  99. package/dist/transports/slack.d.ts.map +1 -0
  100. package/dist/transports/slack.js +844 -0
  101. package/dist/transports/slack.js.map +1 -0
  102. package/dist/transports.d.ts.map +1 -1
  103. package/dist/transports.js +44 -25
  104. package/dist/transports.js.map +1 -1
  105. package/dist/types.d.ts +149 -19
  106. package/dist/types.d.ts.map +1 -1
  107. package/dist/types.js +6 -0
  108. package/dist/types.js.map +1 -1
  109. package/dist/utils/id.d.ts +19 -0
  110. package/dist/utils/id.d.ts.map +1 -0
  111. package/dist/utils/id.js +21 -0
  112. package/dist/utils/id.js.map +1 -0
  113. package/dist/video.d.ts +203 -0
  114. package/dist/video.d.ts.map +1 -0
  115. package/dist/video.js +528 -0
  116. package/dist/video.js.map +1 -0
  117. package/dist/worker.d.ts +343 -0
  118. package/dist/worker.d.ts.map +1 -0
  119. package/dist/worker.js +698 -0
  120. package/dist/worker.js.map +1 -0
  121. package/package.json +24 -5
  122. package/src/actions.ts +48 -38
  123. package/src/agent-comms.ts +1200 -0
  124. package/src/approve.ts +91 -20
  125. package/src/ask.ts +99 -25
  126. package/src/browse.ts +627 -0
  127. package/src/capability-tiers.ts +545 -0
  128. package/src/cascade-context.ts +648 -0
  129. package/src/client.ts +221 -0
  130. package/src/decide.ts +81 -35
  131. package/src/do.ts +98 -52
  132. package/src/error-escalation.ts +1123 -0
  133. package/src/generate.ts +52 -18
  134. package/src/goals.ts +36 -27
  135. package/src/image.ts +816 -0
  136. package/src/index.ts +410 -2
  137. package/src/is.ts +59 -25
  138. package/src/kpis.ts +41 -36
  139. package/src/load-balancing.ts +1467 -0
  140. package/src/logger.ts +93 -0
  141. package/src/notify.ts +78 -17
  142. package/src/role.ts +30 -20
  143. package/src/runtime.ts +796 -0
  144. package/src/team.ts +24 -19
  145. package/src/transports/email.ts +1160 -0
  146. package/src/transports/slack.ts +1320 -0
  147. package/src/transports.ts +58 -43
  148. package/src/types.ts +182 -46
  149. package/src/utils/id.ts +21 -0
  150. package/src/video.ts +906 -0
  151. package/src/worker.ts +1007 -0
  152. package/test/agent-comms.test.ts +1397 -0
  153. package/test/approve.test.ts +305 -0
  154. package/test/ask.test.ts +274 -0
  155. package/test/browse.test.ts +361 -0
  156. package/test/capability-tiers.test.ts +631 -0
  157. package/test/cascade-context.test.ts +692 -0
  158. package/test/decide.test.ts +252 -0
  159. package/test/do.test.ts +144 -0
  160. package/test/error-escalation.test.ts +1205 -0
  161. package/test/error-logging.test.ts +357 -0
  162. package/test/generate.test.ts +319 -0
  163. package/test/image.test.ts +398 -0
  164. package/test/is.test.ts +287 -0
  165. package/test/load-balancing-safety.test.ts +404 -0
  166. package/test/load-balancing-thread-safety.test.ts +464 -0
  167. package/test/load-balancing.test.ts +1145 -0
  168. package/test/notify.test.ts +434 -0
  169. package/test/primitives.test.ts +320 -0
  170. package/test/runtime-integration.test.ts +892 -0
  171. package/test/transports/crypto.test.ts +230 -0
  172. package/test/transports/email.test.ts +866 -0
  173. package/test/transports/id-generation.test.ts +91 -0
  174. package/test/transports/slack.test.ts +760 -0
  175. package/test/type-safety.test.ts +834 -0
  176. package/test/types.test.ts +95 -2
  177. package/test/video.test.ts +530 -0
  178. package/test/worker.test.ts +1433 -0
  179. package/tsconfig.json +4 -1
  180. package/vitest.config.ts +42 -0
  181. package/wrangler.jsonc +36 -0
  182. package/.turbo/turbo-build.log +0 -5
  183. package/src/actions.js +0 -436
  184. package/src/approve.js +0 -234
  185. package/src/ask.js +0 -226
  186. package/src/decide.js +0 -244
  187. package/src/do.js +0 -227
  188. package/src/generate.js +0 -298
  189. package/src/goals.js +0 -205
  190. package/src/index.js +0 -68
  191. package/src/is.js +0 -317
  192. package/src/kpis.js +0 -270
  193. package/src/notify.js +0 -219
  194. package/src/role.js +0 -110
  195. package/src/team.js +0 -130
  196. package/src/transports.js +0 -357
  197. package/src/types.js +0 -71
@@ -0,0 +1,1123 @@
1
+ /**
2
+ * Error Escalation
3
+ *
4
+ * Multi-level error escalation between agent tiers with classification,
5
+ * routing, and recovery patterns.
6
+ *
7
+ * ## Key Concepts
8
+ *
9
+ * 1. **Error Classification** - Categorize errors by severity and type
10
+ * 2. **Escalation Routing** - Determine next tier for escalation
11
+ * 3. **Recovery Patterns** - Retry, fallback, and degradation strategies
12
+ *
13
+ * ## Escalation Flow
14
+ *
15
+ * ```
16
+ * Error -> Classify -> Should Retry? -> Retry with Backoff
17
+ * -> Exhausted -> Should Escalate? -> Escalate to Next Tier
18
+ * -> At Terminal -> Fallback or Degrade
19
+ * ```
20
+ *
21
+ * @packageDocumentation
22
+ */
23
+
24
+ import type { CapabilityTier } from './capability-tiers.js'
25
+ import { getNextTier, TIER_ORDER } from './capability-tiers.js'
26
+
27
+ // ============================================================================
28
+ // Type Definitions - Error Classification
29
+ // ============================================================================
30
+
31
+ /**
32
+ * Error severity levels
33
+ */
34
+ export type ErrorSeverity = 'low' | 'medium' | 'high' | 'critical'
35
+
36
+ /**
37
+ * Error category types
38
+ */
39
+ export type ErrorCategory = 'transient' | 'permanent' | 'escalatable'
40
+
41
+ /**
42
+ * Degradation level for graceful degradation
43
+ */
44
+ export type DegradationLevel = 'none' | 'partial' | 'significant' | 'full'
45
+
46
+ /**
47
+ * Options for determining error severity
48
+ */
49
+ export interface SeverityOptions {
50
+ isRetryable?: boolean
51
+ impact?: 'single-task' | 'workflow' | 'system'
52
+ }
53
+
54
+ /**
55
+ * Classified error with metadata
56
+ */
57
+ export interface ClassifiedError {
58
+ id: string
59
+ original: Error
60
+ severity: ErrorSeverity
61
+ category: ErrorCategory
62
+ tier?: CapabilityTier
63
+ agentId?: string
64
+ taskId?: string
65
+ timestamp: Date
66
+ stack?: string
67
+ previousError?: ClassifiedError
68
+ context?: ErrorContext
69
+ }
70
+
71
+ /**
72
+ * Error context for preservation through escalation
73
+ */
74
+ export interface ErrorContext {
75
+ workflowId?: string
76
+ stepId?: string
77
+ attemptNumber?: number
78
+ startTime?: Date
79
+ metadata?: Record<string, unknown>
80
+ }
81
+
82
+ /**
83
+ * Error chain type
84
+ */
85
+ export type ErrorChain = ClassifiedError[]
86
+
87
+ /**
88
+ * Options for building error chain
89
+ */
90
+ export interface ErrorChainOptions {
91
+ maxDepth?: number
92
+ }
93
+
94
+ // ============================================================================
95
+ // Type Definitions - Escalation Routing
96
+ // ============================================================================
97
+
98
+ /**
99
+ * Escalation path definition
100
+ */
101
+ export interface EscalationPath {
102
+ fromTier: CapabilityTier
103
+ toTier: CapabilityTier
104
+ reason: string
105
+ isTerminal?: boolean
106
+ }
107
+
108
+ /**
109
+ * Escalation rule definition
110
+ */
111
+ export interface EscalationRule {
112
+ name: string
113
+ fromTier: CapabilityTier
114
+ toTier: CapabilityTier
115
+ condition: (error: ClassifiedError) => boolean
116
+ priority?: number
117
+ }
118
+
119
+ /**
120
+ * Tier-specific policy configuration
121
+ */
122
+ export interface TierPolicyConfig {
123
+ maxRetries?: number
124
+ timeout?: number
125
+ allowedCategories?: ErrorCategory[]
126
+ }
127
+
128
+ /**
129
+ * Escalation policy configuration
130
+ */
131
+ export interface EscalationPolicy {
132
+ maxEscalationDepth: number
133
+ allowSkipTiers: boolean
134
+ skipTierThreshold?: ErrorSeverity
135
+ rules: EscalationRule[]
136
+ tierPolicies?: Partial<Record<CapabilityTier, TierPolicyConfig>>
137
+ }
138
+
139
+ /**
140
+ * Options for creating escalation policy
141
+ */
142
+ export interface EscalationPolicyOptions {
143
+ maxEscalationDepth?: number
144
+ allowSkipTiers?: boolean
145
+ skipTierThreshold?: ErrorSeverity
146
+ rules?: EscalationRule[]
147
+ tierPolicies?: Partial<Record<CapabilityTier, TierPolicyConfig>>
148
+ }
149
+
150
+ /**
151
+ * Escalation threshold configuration
152
+ */
153
+ export interface EscalationThreshold {
154
+ errorCount: number
155
+ timeWindow?: number
156
+ severityMultiplier?: Partial<Record<ErrorSeverity, number>>
157
+ }
158
+
159
+ /**
160
+ * Error history entry for threshold checking
161
+ */
162
+ export interface ErrorHistoryEntry {
163
+ timestamp: number
164
+ severity?: ErrorSeverity
165
+ }
166
+
167
+ /**
168
+ * Escalation validation result
169
+ */
170
+ export interface EscalationValidationResult {
171
+ valid: boolean
172
+ error?: string
173
+ }
174
+
175
+ /**
176
+ * Escalation result from engine
177
+ */
178
+ export interface EscalationResult {
179
+ handled: boolean
180
+ action: 'retry' | 'escalate' | 'fallback' | 'degrade' | 'terminal'
181
+ classifiedError: ClassifiedError
182
+ retryDelay?: number
183
+ escalationPath?: EscalationPath
184
+ fallbackAgent?: AgentForFallback
185
+ degradationLevel?: DegradationLevel
186
+ preservedContext?: ErrorContext
187
+ errorChain?: ErrorChain
188
+ }
189
+
190
+ // ============================================================================
191
+ // Type Definitions - Recovery Patterns
192
+ // ============================================================================
193
+
194
+ /**
195
+ * Retry configuration
196
+ */
197
+ export interface RetryConfig {
198
+ maxRetries: number
199
+ baseDelayMs: number
200
+ maxDelayMs?: number
201
+ backoffMultiplier?: number
202
+ jitterPercent?: number
203
+ retryableCategories?: ErrorCategory[]
204
+ }
205
+
206
+ /**
207
+ * Retry state
208
+ */
209
+ export interface RetryState {
210
+ attemptNumber: number
211
+ lastAttemptTime: Date | null
212
+ nextRetryTime: Date | null
213
+ exhausted: boolean
214
+ }
215
+
216
+ /**
217
+ * Agent info for fallback selection
218
+ */
219
+ export interface AgentForFallback {
220
+ id: string
221
+ tier: CapabilityTier
222
+ skills?: string[]
223
+ currentLoad?: number
224
+ maxLoad?: number
225
+ }
226
+
227
+ /**
228
+ * Fallback configuration
229
+ */
230
+ export interface FallbackConfig {
231
+ strategy: 'capability-match' | 'least-loaded' | 'same-tier' | 'round-robin'
232
+ requiredSkills?: string[]
233
+ currentTier?: CapabilityTier
234
+ excludeAgentIds?: string[]
235
+ }
236
+
237
+ /**
238
+ * Degradation options
239
+ */
240
+ export interface DegradationOptions {
241
+ errorCount?: number
242
+ threshold?: number
243
+ rules?: Array<{
244
+ condition: (severity: ErrorSeverity) => boolean
245
+ level: DegradationLevel
246
+ }>
247
+ }
248
+
249
+ /**
250
+ * Recovery state
251
+ */
252
+ export interface RecoveryState {
253
+ errorId: string
254
+ tier: CapabilityTier
255
+ agentId?: string
256
+ retryState: RetryState
257
+ escalated: boolean
258
+ resolved: boolean
259
+ escalationPath: CapabilityTier[]
260
+ fallbackHistory: string[]
261
+ lastAction?: string
262
+ resolution?: string
263
+ isTerminal?: boolean
264
+ }
265
+
266
+ /**
267
+ * Recovery state options
268
+ */
269
+ export interface RecoveryStateOptions {
270
+ errorId: string
271
+ tier: CapabilityTier
272
+ agentId?: string
273
+ }
274
+
275
+ /**
276
+ * Recovery state update
277
+ */
278
+ export interface RecoveryStateUpdate {
279
+ type: 'retry' | 'escalate' | 'fallback' | 'resolve'
280
+ success?: boolean
281
+ exhausted?: boolean
282
+ toTier?: CapabilityTier
283
+ toAgentId?: string
284
+ resolution?: string
285
+ isTerminal?: boolean
286
+ }
287
+
288
+ // ============================================================================
289
+ // Type Definitions - Escalation Engine
290
+ // ============================================================================
291
+
292
+ /**
293
+ * Escalation engine options
294
+ */
295
+ export interface EscalationEngineOptions {
296
+ policy?: EscalationPolicy
297
+ retryConfig?: RetryConfig
298
+ }
299
+
300
+ /**
301
+ * Error handling options
302
+ */
303
+ export interface HandleErrorOptions {
304
+ tier: CapabilityTier
305
+ agentId?: string
306
+ taskId?: string
307
+ severity?: ErrorSeverity
308
+ retryState?: RetryState
309
+ escalationHistory?: CapabilityTier[]
310
+ previousError?: ClassifiedError
311
+ context?: ErrorContext
312
+ availableAgents?: AgentForFallback[]
313
+ simulateRetrySuccess?: boolean
314
+ simulateRetryFailure?: boolean
315
+ }
316
+
317
+ /**
318
+ * Escalation metrics
319
+ */
320
+ export interface EscalationMetrics {
321
+ totalErrors: number
322
+ bySeverity: Record<ErrorSeverity, number>
323
+ escalationsByTier: Record<string, number>
324
+ retrySuccessRate: number
325
+ retriesTotal: number
326
+ retriesSuccessful: number
327
+ }
328
+
329
+ /**
330
+ * Escalation engine interface
331
+ */
332
+ export interface EscalationEngine {
333
+ handleError(error: Error, options: HandleErrorOptions): Promise<EscalationResult>
334
+ getMetrics(): EscalationMetrics
335
+ reset(): void
336
+ }
337
+
338
+ // ============================================================================
339
+ // Error Classification Implementation
340
+ // ============================================================================
341
+
342
+ let errorIdCounter = 0
343
+
344
+ /**
345
+ * Generate a unique error ID
346
+ */
347
+ function generateErrorId(): string {
348
+ return `err-${Date.now()}-${++errorIdCounter}`
349
+ }
350
+
351
+ /**
352
+ * Get error severity based on error and options
353
+ */
354
+ export function getErrorSeverity(error: Error, options: SeverityOptions = {}): ErrorSeverity {
355
+ const { isRetryable = true, impact = 'single-task' } = options
356
+
357
+ // Critical: system-wide impact and not retryable
358
+ if (impact === 'system' && !isRetryable) {
359
+ return 'critical'
360
+ }
361
+
362
+ // High: workflow impact and not retryable
363
+ if (impact === 'workflow' && !isRetryable) {
364
+ return 'high'
365
+ }
366
+
367
+ // Medium: workflow impact but retryable
368
+ if (impact === 'workflow' && isRetryable) {
369
+ return 'medium'
370
+ }
371
+
372
+ // Low: single-task impact
373
+ return 'low'
374
+ }
375
+
376
+ /**
377
+ * Get error category based on error type
378
+ */
379
+ export function getErrorCategory(error: Error): ErrorCategory {
380
+ const name = error.name || ''
381
+ const message = error.message || ''
382
+
383
+ // Permanent errors - configuration, validation, etc.
384
+ if (
385
+ name.includes('Configuration') ||
386
+ name.includes('Validation') ||
387
+ name.includes('Invalid') ||
388
+ message.includes('invalid')
389
+ ) {
390
+ return 'permanent'
391
+ }
392
+
393
+ // Escalatable errors - require higher tier
394
+ if (
395
+ name.includes('Approval') ||
396
+ name.includes('Escalation') ||
397
+ message.includes('approval') ||
398
+ message.includes('human')
399
+ ) {
400
+ return 'escalatable'
401
+ }
402
+
403
+ // Default to transient (retryable)
404
+ return 'transient'
405
+ }
406
+
407
+ /**
408
+ * Create a classified error
409
+ */
410
+ export function createClassifiedError(
411
+ original: Error,
412
+ options: {
413
+ severity: ErrorSeverity
414
+ category: ErrorCategory
415
+ tier?: CapabilityTier
416
+ agentId?: string
417
+ taskId?: string
418
+ previousError?: ClassifiedError
419
+ context?: ErrorContext
420
+ }
421
+ ): ClassifiedError {
422
+ return {
423
+ id: generateErrorId(),
424
+ original,
425
+ severity: options.severity,
426
+ category: options.category,
427
+ timestamp: new Date(),
428
+ ...(original.stack !== undefined && { stack: original.stack }),
429
+ ...(options.tier !== undefined && { tier: options.tier }),
430
+ ...(options.agentId !== undefined && { agentId: options.agentId }),
431
+ ...(options.taskId !== undefined && { taskId: options.taskId }),
432
+ ...(options.previousError !== undefined && { previousError: options.previousError }),
433
+ ...(options.context !== undefined && { context: options.context }),
434
+ }
435
+ }
436
+
437
+ /**
438
+ * Classify an error automatically
439
+ */
440
+ export function classifyError(
441
+ error: Error,
442
+ options: Partial<{
443
+ tier: CapabilityTier
444
+ agentId: string
445
+ taskId: string
446
+ context: ErrorContext
447
+ }> = {}
448
+ ): ClassifiedError {
449
+ const severity = getErrorSeverity(error)
450
+ const category = getErrorCategory(error)
451
+
452
+ return createClassifiedError(error, {
453
+ severity,
454
+ category,
455
+ ...options,
456
+ })
457
+ }
458
+
459
+ /**
460
+ * Check if error is escalatable
461
+ */
462
+ export function isEscalatable(error: ClassifiedError): boolean {
463
+ // Critical errors are always escalatable
464
+ if (error.severity === 'critical') {
465
+ return true
466
+ }
467
+
468
+ // Escalatable category
469
+ if (error.category === 'escalatable') {
470
+ return true
471
+ }
472
+
473
+ // High severity transient errors
474
+ if (error.severity === 'high' && error.category === 'transient') {
475
+ return true
476
+ }
477
+
478
+ return false
479
+ }
480
+
481
+ /**
482
+ * Preserve context through escalation
483
+ */
484
+ export function preserveContext(
485
+ context: ErrorContext,
486
+ updates: Partial<ErrorContext> = {}
487
+ ): ErrorContext {
488
+ return {
489
+ ...context,
490
+ ...updates,
491
+ metadata: {
492
+ ...context.metadata,
493
+ ...updates.metadata,
494
+ },
495
+ }
496
+ }
497
+
498
+ /**
499
+ * Build error chain from escalation history
500
+ */
501
+ export function buildErrorChain(
502
+ error: ClassifiedError,
503
+ options: ErrorChainOptions = {}
504
+ ): ErrorChain {
505
+ const { maxDepth = 50 } = options
506
+ const chain: ErrorChain = []
507
+ let current: ClassifiedError | undefined = error
508
+ let depth = 0
509
+
510
+ while (current && depth < maxDepth) {
511
+ chain.unshift(current)
512
+ current = current.previousError
513
+ depth++
514
+ }
515
+
516
+ return chain
517
+ }
518
+
519
+ // ============================================================================
520
+ // Escalation Routing Implementation
521
+ // ============================================================================
522
+
523
+ const SEVERITY_ORDER: Record<ErrorSeverity, number> = {
524
+ low: 0,
525
+ medium: 1,
526
+ high: 2,
527
+ critical: 3,
528
+ }
529
+
530
+ /**
531
+ * Create an escalation policy with defaults
532
+ */
533
+ export function createEscalationPolicy(options: EscalationPolicyOptions): EscalationPolicy {
534
+ return {
535
+ maxEscalationDepth: options.maxEscalationDepth ?? 10,
536
+ allowSkipTiers: options.allowSkipTiers ?? false,
537
+ ...(options.skipTierThreshold !== undefined && {
538
+ skipTierThreshold: options.skipTierThreshold,
539
+ }),
540
+ rules: options.rules ?? [],
541
+ ...(options.tierPolicies !== undefined && { tierPolicies: options.tierPolicies }),
542
+ }
543
+ }
544
+
545
+ /**
546
+ * Get next escalation tier based on error and policy
547
+ */
548
+ export function getNextEscalationTier(
549
+ error: ClassifiedError,
550
+ policy: EscalationPolicy
551
+ ): CapabilityTier | null {
552
+ const currentTier = error.tier ?? 'code'
553
+
554
+ // Check custom rules first
555
+ const applicableRules = policy.rules
556
+ .filter((rule) => rule.fromTier === currentTier && rule.condition(error))
557
+ .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))
558
+
559
+ if (applicableRules.length > 0) {
560
+ return applicableRules[0]!.toTier
561
+ }
562
+
563
+ // Check if we should skip tiers
564
+ if (
565
+ policy.allowSkipTiers &&
566
+ policy.skipTierThreshold &&
567
+ SEVERITY_ORDER[error.severity] >= SEVERITY_ORDER[policy.skipTierThreshold]
568
+ ) {
569
+ return 'human'
570
+ }
571
+
572
+ // Default: go to next tier
573
+ return getNextTier(currentTier)
574
+ }
575
+
576
+ /**
577
+ * Determine escalation path for an error
578
+ */
579
+ export function determineEscalationPath(
580
+ error: ClassifiedError,
581
+ policy?: EscalationPolicy
582
+ ): EscalationPath {
583
+ const fromTier = error.tier ?? 'code'
584
+ const effectivePolicy = policy ?? createEscalationPolicy({})
585
+ const nextTier = getNextEscalationTier(error, effectivePolicy)
586
+
587
+ if (!nextTier || nextTier === fromTier) {
588
+ return {
589
+ fromTier,
590
+ toTier: fromTier,
591
+ reason: 'Cannot escalate further - at terminal tier',
592
+ isTerminal: true,
593
+ }
594
+ }
595
+
596
+ return {
597
+ fromTier,
598
+ toTier: nextTier,
599
+ reason: `Escalating due to ${error.severity} severity ${error.category} error`,
600
+ }
601
+ }
602
+
603
+ /**
604
+ * Check if escalation should occur based on threshold
605
+ */
606
+ export function shouldEscalate(
607
+ threshold: EscalationThreshold,
608
+ errorHistory: ErrorHistoryEntry[]
609
+ ): boolean {
610
+ const now = Date.now()
611
+ const { errorCount, timeWindow, severityMultiplier } = threshold
612
+
613
+ // Filter by time window if specified
614
+ let relevantErrors = errorHistory
615
+ if (timeWindow) {
616
+ relevantErrors = errorHistory.filter((entry) => now - entry.timestamp <= timeWindow)
617
+ }
618
+
619
+ // Calculate weighted count if severity multiplier is specified
620
+ if (severityMultiplier) {
621
+ const weightedCount = relevantErrors.reduce((sum, entry) => {
622
+ const multiplier = entry.severity ? severityMultiplier[entry.severity] ?? 1 : 1
623
+ return sum + multiplier
624
+ }, 0)
625
+ return weightedCount >= errorCount
626
+ }
627
+
628
+ return relevantErrors.length >= errorCount
629
+ }
630
+
631
+ /**
632
+ * Detect circular escalation
633
+ */
634
+ export function detectCircularEscalation(path: EscalationPath, history: CapabilityTier[]): boolean {
635
+ // If we're going back to a tier we've already visited, it's circular
636
+ if (history.includes(path.toTier)) {
637
+ return true
638
+ }
639
+
640
+ // Check for de-escalation (going to a lower tier)
641
+ if (TIER_ORDER[path.toTier] < TIER_ORDER[path.fromTier]) {
642
+ // If we've already been at a tier higher than where we're going, it's circular
643
+ const maxTierVisited = Math.max(...history.map((t) => TIER_ORDER[t]))
644
+ if (TIER_ORDER[path.toTier] < maxTierVisited) {
645
+ return true
646
+ }
647
+ }
648
+
649
+ return false
650
+ }
651
+
652
+ /**
653
+ * Validate an escalation path
654
+ */
655
+ export function validateEscalationPath(
656
+ path: EscalationPath,
657
+ history: CapabilityTier[],
658
+ policy: EscalationPolicy
659
+ ): EscalationValidationResult {
660
+ // Check max depth
661
+ if (history.length >= policy.maxEscalationDepth) {
662
+ return {
663
+ valid: false,
664
+ error: `Escalation depth exceeded: max is ${policy.maxEscalationDepth}`,
665
+ }
666
+ }
667
+
668
+ // Check for circular escalation
669
+ if (detectCircularEscalation(path, history)) {
670
+ return {
671
+ valid: false,
672
+ error: 'Circular escalation detected',
673
+ }
674
+ }
675
+
676
+ return { valid: true }
677
+ }
678
+
679
+ // ============================================================================
680
+ // Recovery Patterns Implementation
681
+ // ============================================================================
682
+
683
+ /**
684
+ * Calculate backoff delay with optional jitter
685
+ */
686
+ export function calculateBackoff(config: RetryConfig, attemptNumber: number): number {
687
+ const { baseDelayMs, maxDelayMs = Infinity, backoffMultiplier = 2, jitterPercent = 0 } = config
688
+
689
+ // Calculate exponential delay
690
+ let delay = baseDelayMs * Math.pow(backoffMultiplier, attemptNumber)
691
+
692
+ // Cap at max delay
693
+ delay = Math.min(delay, maxDelayMs)
694
+
695
+ // Add jitter if configured
696
+ if (jitterPercent > 0) {
697
+ const jitterRange = delay * (jitterPercent / 100)
698
+ const jitter = (Math.random() * 2 - 1) * jitterRange
699
+ delay = delay + jitter
700
+ }
701
+
702
+ return Math.round(delay)
703
+ }
704
+
705
+ /**
706
+ * Create initial retry state
707
+ */
708
+ export function createRetryState(config: Partial<RetryConfig> = {}): RetryState {
709
+ return {
710
+ attemptNumber: 0,
711
+ lastAttemptTime: null,
712
+ nextRetryTime: null,
713
+ exhausted: false,
714
+ }
715
+ }
716
+
717
+ /**
718
+ * Check if retry should occur
719
+ */
720
+ export function shouldRetry(
721
+ config: RetryConfig,
722
+ state: RetryState,
723
+ error?: ClassifiedError
724
+ ): boolean {
725
+ // Already exhausted
726
+ if (state.exhausted) {
727
+ return false
728
+ }
729
+
730
+ // Exceeded max retries
731
+ if (state.attemptNumber >= config.maxRetries) {
732
+ return false
733
+ }
734
+
735
+ // Check if error category is retryable
736
+ if (error && config.retryableCategories) {
737
+ if (!config.retryableCategories.includes(error.category)) {
738
+ return false
739
+ }
740
+ }
741
+
742
+ return true
743
+ }
744
+
745
+ /**
746
+ * Select a fallback agent
747
+ */
748
+ export function selectFallbackAgent(
749
+ config: FallbackConfig,
750
+ agents: AgentForFallback[]
751
+ ): AgentForFallback | null {
752
+ // Filter excluded agents
753
+ let candidates = agents.filter((a) => !config.excludeAgentIds?.includes(a.id))
754
+
755
+ if (candidates.length === 0) {
756
+ return null
757
+ }
758
+
759
+ switch (config.strategy) {
760
+ case 'capability-match':
761
+ if (config.requiredSkills && config.requiredSkills.length > 0) {
762
+ candidates = candidates.filter((a) => {
763
+ const agentSkills = a.skills ?? []
764
+ return config.requiredSkills!.every((skill) => agentSkills.includes(skill))
765
+ })
766
+ }
767
+ return candidates[0] ?? null
768
+
769
+ case 'least-loaded':
770
+ candidates.sort((a, b) => {
771
+ const loadA = (a.currentLoad ?? 0) / (a.maxLoad ?? 1)
772
+ const loadB = (b.currentLoad ?? 0) / (b.maxLoad ?? 1)
773
+ return loadA - loadB
774
+ })
775
+ return candidates[0] ?? null
776
+
777
+ case 'same-tier':
778
+ if (config.currentTier) {
779
+ candidates = candidates.filter((a) => a.tier === config.currentTier)
780
+ }
781
+ return candidates[0] ?? null
782
+
783
+ case 'round-robin':
784
+ default:
785
+ return candidates[0] ?? null
786
+ }
787
+ }
788
+
789
+ /**
790
+ * Get degradation level based on severity and options
791
+ */
792
+ export function getDegradationLevel(
793
+ severity: ErrorSeverity,
794
+ options: DegradationOptions = {}
795
+ ): DegradationLevel {
796
+ // Check custom rules first
797
+ if (options.rules) {
798
+ for (const rule of options.rules) {
799
+ if (rule.condition(severity)) {
800
+ return rule.level
801
+ }
802
+ }
803
+ }
804
+
805
+ // Check error count threshold
806
+ if (options.errorCount !== undefined && options.threshold !== undefined) {
807
+ if (options.errorCount >= options.threshold) {
808
+ // Bump up degradation level
809
+ switch (severity) {
810
+ case 'low':
811
+ return 'partial'
812
+ case 'medium':
813
+ return 'significant'
814
+ case 'high':
815
+ case 'critical':
816
+ return 'full'
817
+ }
818
+ }
819
+ }
820
+
821
+ // Default based on severity
822
+ switch (severity) {
823
+ case 'low':
824
+ return 'none'
825
+ case 'medium':
826
+ return 'partial'
827
+ case 'high':
828
+ return 'significant'
829
+ case 'critical':
830
+ return 'full'
831
+ default:
832
+ return 'none'
833
+ }
834
+ }
835
+
836
+ /**
837
+ * Create initial recovery state
838
+ */
839
+ export function createRecoveryState(options: RecoveryStateOptions): RecoveryState {
840
+ return {
841
+ errorId: options.errorId,
842
+ tier: options.tier,
843
+ ...(options.agentId !== undefined && { agentId: options.agentId }),
844
+ retryState: createRetryState(),
845
+ escalated: false,
846
+ resolved: false,
847
+ escalationPath: [options.tier],
848
+ fallbackHistory: options.agentId ? [options.agentId] : [],
849
+ }
850
+ }
851
+
852
+ /**
853
+ * Update recovery state
854
+ */
855
+ export function updateRecoveryState(
856
+ state: RecoveryState,
857
+ update: RecoveryStateUpdate
858
+ ): RecoveryState {
859
+ switch (update.type) {
860
+ case 'retry':
861
+ return {
862
+ ...state,
863
+ retryState: {
864
+ ...state.retryState,
865
+ attemptNumber: state.retryState.attemptNumber + 1,
866
+ lastAttemptTime: new Date(),
867
+ exhausted: update.exhausted ?? state.retryState.exhausted,
868
+ },
869
+ lastAction: 'retry',
870
+ }
871
+
872
+ case 'escalate':
873
+ const newTier = update.toTier ?? state.tier
874
+ return {
875
+ ...state,
876
+ tier: newTier,
877
+ escalated: true,
878
+ escalationPath: [...state.escalationPath, newTier],
879
+ lastAction: 'escalate',
880
+ ...(update.isTerminal !== undefined && { isTerminal: update.isTerminal }),
881
+ }
882
+
883
+ case 'fallback':
884
+ const oldAgentId = state.agentId
885
+ return {
886
+ ...state,
887
+ ...(update.toAgentId !== undefined && { agentId: update.toAgentId }),
888
+ fallbackHistory: oldAgentId
889
+ ? [...state.fallbackHistory, oldAgentId]
890
+ : state.fallbackHistory,
891
+ lastAction: 'fallback',
892
+ }
893
+
894
+ case 'resolve':
895
+ return {
896
+ ...state,
897
+ resolved: true,
898
+ ...(update.resolution !== undefined && { resolution: update.resolution }),
899
+ lastAction: 'resolve',
900
+ }
901
+
902
+ default:
903
+ return state
904
+ }
905
+ }
906
+
907
+ /**
908
+ * Check if error is recoverable
909
+ */
910
+ export function isRecoverable(state: RecoveryState): boolean {
911
+ // If resolved, not recoverable (already done)
912
+ if (state.resolved) {
913
+ return false
914
+ }
915
+
916
+ // If retries not exhausted, recoverable
917
+ if (!state.retryState.exhausted) {
918
+ return true
919
+ }
920
+
921
+ // If at terminal tier and retries exhausted, not recoverable
922
+ if (state.isTerminal && state.retryState.exhausted) {
923
+ return false
924
+ }
925
+
926
+ // If not at human tier, can still escalate
927
+ if (state.tier !== 'human') {
928
+ return true
929
+ }
930
+
931
+ // At human tier with exhausted retries - not recoverable
932
+ return false
933
+ }
934
+
935
+ // ============================================================================
936
+ // Escalation Engine Implementation
937
+ // ============================================================================
938
+
939
+ /**
940
+ * Create an escalation engine
941
+ */
942
+ export function createEscalationEngine(options: EscalationEngineOptions = {}): EscalationEngine {
943
+ const policy = options.policy ?? createEscalationPolicy({})
944
+ const retryConfig: RetryConfig = options.retryConfig ?? {
945
+ maxRetries: 3,
946
+ baseDelayMs: 100,
947
+ maxDelayMs: 30000,
948
+ backoffMultiplier: 2,
949
+ }
950
+
951
+ // Metrics tracking
952
+ let metrics: EscalationMetrics = {
953
+ totalErrors: 0,
954
+ bySeverity: { low: 0, medium: 0, high: 0, critical: 0 },
955
+ escalationsByTier: {},
956
+ retrySuccessRate: 0,
957
+ retriesTotal: 0,
958
+ retriesSuccessful: 0,
959
+ }
960
+
961
+ async function handleError(error: Error, opts: HandleErrorOptions): Promise<EscalationResult> {
962
+ // Classify the error
963
+ const severity = opts.severity ?? getErrorSeverity(error)
964
+ const category = getErrorCategory(error)
965
+
966
+ const classifiedError = createClassifiedError(error, {
967
+ severity,
968
+ category,
969
+ ...(opts.tier !== undefined && { tier: opts.tier }),
970
+ ...(opts.agentId !== undefined && { agentId: opts.agentId }),
971
+ ...(opts.taskId !== undefined && { taskId: opts.taskId }),
972
+ ...(opts.previousError !== undefined && { previousError: opts.previousError }),
973
+ ...(opts.context !== undefined && { context: opts.context }),
974
+ })
975
+
976
+ // Update metrics
977
+ metrics.totalErrors++
978
+ metrics.bySeverity[severity]++
979
+
980
+ // Get retry state
981
+ const retryState = opts.retryState ?? createRetryState()
982
+
983
+ // Get tier-specific config
984
+ const tierPolicy = policy.tierPolicies?.[opts.tier]
985
+ const effectiveRetryConfig: RetryConfig = {
986
+ ...retryConfig,
987
+ maxRetries: tierPolicy?.maxRetries ?? retryConfig.maxRetries,
988
+ }
989
+
990
+ // Determine action
991
+ let action: EscalationResult['action']
992
+ let retryDelay: number | undefined
993
+ let escalationPath: EscalationPath | undefined
994
+ let fallbackAgent: AgentForFallback | undefined
995
+ let degradationLevel: DegradationLevel | undefined
996
+
997
+ // Check for simulated retry results
998
+ if (opts.simulateRetrySuccess) {
999
+ metrics.retriesTotal++
1000
+ metrics.retriesSuccessful++
1001
+ metrics.retrySuccessRate = metrics.retriesSuccessful / metrics.retriesTotal
1002
+ } else if (opts.simulateRetryFailure) {
1003
+ metrics.retriesTotal++
1004
+ metrics.retrySuccessRate = metrics.retriesSuccessful / metrics.retriesTotal
1005
+ }
1006
+
1007
+ // Should we retry?
1008
+ if (!retryState.exhausted && shouldRetry(effectiveRetryConfig, retryState, classifiedError)) {
1009
+ action = 'retry'
1010
+ retryDelay = calculateBackoff(effectiveRetryConfig, retryState.attemptNumber)
1011
+ metrics.retriesTotal++
1012
+ }
1013
+ // Should we escalate?
1014
+ else if (opts.tier !== 'human') {
1015
+ // Check escalation history depth
1016
+ const history = opts.escalationHistory ?? [opts.tier]
1017
+ const path = determineEscalationPath(classifiedError, policy)
1018
+ const validation = validateEscalationPath(path, history, policy)
1019
+
1020
+ if (validation.valid && !path.isTerminal) {
1021
+ action = 'escalate'
1022
+ escalationPath = path
1023
+ metrics.escalationsByTier[opts.tier] = (metrics.escalationsByTier[opts.tier] ?? 0) + 1
1024
+ } else if (opts.availableAgents && opts.availableAgents.length > 0) {
1025
+ // Try fallback
1026
+ action = 'fallback'
1027
+ fallbackAgent =
1028
+ selectFallbackAgent(
1029
+ { strategy: 'capability-match', excludeAgentIds: [opts.agentId ?? ''] },
1030
+ opts.availableAgents
1031
+ ) ?? undefined
1032
+ } else {
1033
+ action = 'degrade'
1034
+ degradationLevel = getDegradationLevel(severity)
1035
+ }
1036
+ }
1037
+ // At human tier - try fallback or degrade
1038
+ else {
1039
+ if (opts.availableAgents && opts.availableAgents.length > 0) {
1040
+ action = 'fallback'
1041
+ fallbackAgent =
1042
+ selectFallbackAgent(
1043
+ { strategy: 'same-tier', currentTier: 'human', excludeAgentIds: [opts.agentId ?? ''] },
1044
+ opts.availableAgents
1045
+ ) ?? undefined
1046
+ if (!fallbackAgent) {
1047
+ action = 'terminal'
1048
+ }
1049
+ } else {
1050
+ action = 'terminal'
1051
+ }
1052
+ }
1053
+
1054
+ return {
1055
+ handled: true,
1056
+ action,
1057
+ classifiedError,
1058
+ ...(retryDelay !== undefined && { retryDelay }),
1059
+ ...(escalationPath !== undefined && { escalationPath }),
1060
+ ...(fallbackAgent !== undefined && { fallbackAgent }),
1061
+ ...(degradationLevel !== undefined && { degradationLevel }),
1062
+ ...(opts.context !== undefined && { preservedContext: opts.context }),
1063
+ ...(opts.previousError !== undefined && { errorChain: buildErrorChain(classifiedError) }),
1064
+ }
1065
+ }
1066
+
1067
+ function getMetrics(): EscalationMetrics {
1068
+ return { ...metrics }
1069
+ }
1070
+
1071
+ function reset(): void {
1072
+ metrics = {
1073
+ totalErrors: 0,
1074
+ bySeverity: { low: 0, medium: 0, high: 0, critical: 0 },
1075
+ escalationsByTier: {},
1076
+ retrySuccessRate: 0,
1077
+ retriesTotal: 0,
1078
+ retriesSuccessful: 0,
1079
+ }
1080
+ }
1081
+
1082
+ return {
1083
+ handleError,
1084
+ getMetrics,
1085
+ reset,
1086
+ }
1087
+ }
1088
+
1089
+ // ============================================================================
1090
+ // Default Export
1091
+ // ============================================================================
1092
+
1093
+ export default {
1094
+ // Error Classification
1095
+ getErrorSeverity,
1096
+ getErrorCategory,
1097
+ createClassifiedError,
1098
+ classifyError,
1099
+ isEscalatable,
1100
+ preserveContext,
1101
+ buildErrorChain,
1102
+
1103
+ // Escalation Routing
1104
+ createEscalationPolicy,
1105
+ getNextEscalationTier,
1106
+ determineEscalationPath,
1107
+ shouldEscalate,
1108
+ detectCircularEscalation,
1109
+ validateEscalationPath,
1110
+
1111
+ // Recovery Patterns
1112
+ calculateBackoff,
1113
+ createRetryState,
1114
+ shouldRetry,
1115
+ selectFallbackAgent,
1116
+ getDegradationLevel,
1117
+ createRecoveryState,
1118
+ updateRecoveryState,
1119
+ isRecoverable,
1120
+
1121
+ // Escalation Engine
1122
+ createEscalationEngine,
1123
+ }