digital-workers 2.1.1 → 2.1.3

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 (51) hide show
  1. package/.turbo/turbo-build.log +4 -5
  2. package/CHANGELOG.md +14 -0
  3. package/LICENSE +21 -0
  4. package/README.md +134 -180
  5. package/dist/actions.d.ts.map +1 -1
  6. package/dist/actions.js +1 -0
  7. package/dist/actions.js.map +1 -1
  8. package/dist/agent-comms.d.ts +438 -0
  9. package/dist/agent-comms.d.ts.map +1 -0
  10. package/dist/agent-comms.js +666 -0
  11. package/dist/agent-comms.js.map +1 -0
  12. package/dist/capability-tiers.d.ts +230 -0
  13. package/dist/capability-tiers.d.ts.map +1 -0
  14. package/dist/capability-tiers.js +388 -0
  15. package/dist/capability-tiers.js.map +1 -0
  16. package/dist/cascade-context.d.ts +523 -0
  17. package/dist/cascade-context.d.ts.map +1 -0
  18. package/dist/cascade-context.js +494 -0
  19. package/dist/cascade-context.js.map +1 -0
  20. package/dist/error-escalation.d.ts +416 -0
  21. package/dist/error-escalation.d.ts.map +1 -0
  22. package/dist/error-escalation.js +656 -0
  23. package/dist/error-escalation.js.map +1 -0
  24. package/dist/index.d.ts +10 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +34 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/load-balancing.d.ts +395 -0
  29. package/dist/load-balancing.d.ts.map +1 -0
  30. package/dist/load-balancing.js +905 -0
  31. package/dist/load-balancing.js.map +1 -0
  32. package/dist/types.d.ts +8 -0
  33. package/dist/types.d.ts.map +1 -1
  34. package/dist/types.js +1 -0
  35. package/dist/types.js.map +1 -1
  36. package/package.json +14 -13
  37. package/src/actions.ts +9 -8
  38. package/src/agent-comms.ts +1238 -0
  39. package/src/capability-tiers.ts +545 -0
  40. package/src/cascade-context.ts +648 -0
  41. package/src/error-escalation.ts +1135 -0
  42. package/src/index.ts +223 -0
  43. package/src/load-balancing.ts +1381 -0
  44. package/src/types.ts +8 -0
  45. package/test/agent-comms.test.ts +1397 -0
  46. package/test/capability-tiers.test.ts +631 -0
  47. package/test/cascade-context.test.ts +692 -0
  48. package/test/error-escalation.test.ts +1205 -0
  49. package/test/load-balancing-thread-safety.test.ts +464 -0
  50. package/test/load-balancing.test.ts +1145 -0
  51. package/test/types.test.ts +35 -0
@@ -0,0 +1,648 @@
1
+ /**
2
+ * Type-safe cascade context schema for agent coordination
3
+ *
4
+ * Provides structured data flow and coordination between agents in multi-agent
5
+ * workflows. Enables type-safe context propagation, validation, enrichment,
6
+ * and serialization for distributed agent systems.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+
11
+ import { z } from 'zod'
12
+
13
+ // ============================================================================
14
+ // Agent Tier Schema and Type
15
+ // ============================================================================
16
+
17
+ /**
18
+ * Agent tier levels for cascade hierarchies
19
+ *
20
+ * - coordinator: Orchestrates multiple agents, highest level
21
+ * - supervisor: Manages worker agents, handles escalation
22
+ * - worker: Executes standard tasks
23
+ * - specialist: Handles domain-specific tasks
24
+ * - executor: Low-level task execution
25
+ */
26
+ export const AgentTierSchema = z.enum([
27
+ 'coordinator',
28
+ 'supervisor',
29
+ 'worker',
30
+ 'specialist',
31
+ 'executor',
32
+ ])
33
+
34
+ export type AgentTier = z.infer<typeof AgentTierSchema>
35
+
36
+ // ============================================================================
37
+ // Context Version Schema and Type
38
+ // ============================================================================
39
+
40
+ /**
41
+ * Version tracking for context evolution and debugging
42
+ */
43
+ export const ContextVersionSchema = z.object({
44
+ major: z.number().int().nonnegative(),
45
+ minor: z.number().int().nonnegative(),
46
+ patch: z.number().int().nonnegative(),
47
+ timestamp: z.date(),
48
+ hash: z.string().optional(),
49
+ })
50
+
51
+ export type ContextVersion = z.infer<typeof ContextVersionSchema>
52
+
53
+ // ============================================================================
54
+ // Agent Reference Schema
55
+ // ============================================================================
56
+
57
+ /**
58
+ * Reference to an agent with tier information
59
+ */
60
+ export const AgentRefSchema = z.object({
61
+ id: z.string().min(1),
62
+ tier: AgentTierSchema,
63
+ name: z.string().optional(),
64
+ capabilities: z.array(z.string()).optional(),
65
+ })
66
+
67
+ export type AgentRef = z.infer<typeof AgentRefSchema>
68
+
69
+ // ============================================================================
70
+ // Task Priority Schema
71
+ // ============================================================================
72
+
73
+ export const TaskPrioritySchema = z.enum(['critical', 'high', 'normal', 'low'])
74
+ export type TaskPriority = z.infer<typeof TaskPrioritySchema>
75
+
76
+ // ============================================================================
77
+ // Task Schema
78
+ // ============================================================================
79
+
80
+ export const TaskInfoSchema = z.object({
81
+ id: z.string().min(1),
82
+ type: z.string().min(1),
83
+ priority: TaskPrioritySchema,
84
+ description: z.string(),
85
+ deadline: z.date().optional(),
86
+ parentTaskId: z.string().optional(),
87
+ })
88
+
89
+ export type TaskInfo = z.infer<typeof TaskInfoSchema>
90
+
91
+ // ============================================================================
92
+ // Execution Phase Schema
93
+ // ============================================================================
94
+
95
+ export const ExecutionPhaseSchema = z.enum([
96
+ 'planning',
97
+ 'execution',
98
+ 'validation',
99
+ 'escalation',
100
+ 'completed',
101
+ 'failed',
102
+ ])
103
+
104
+ export type ExecutionPhase = z.infer<typeof ExecutionPhaseSchema>
105
+
106
+ // ============================================================================
107
+ // Execution State Schema
108
+ // ============================================================================
109
+
110
+ export const ExecutionStateSchema = z.object({
111
+ phase: ExecutionPhaseSchema,
112
+ startedAt: z.date(),
113
+ completedAt: z.date().optional(),
114
+ attempts: z.number().int().nonnegative(),
115
+ lastError: z.string().optional(),
116
+ escalatedFrom: z.string().optional(),
117
+ })
118
+
119
+ export type ExecutionState = z.infer<typeof ExecutionStateSchema>
120
+
121
+ // ============================================================================
122
+ // Trace Entry Schema
123
+ // ============================================================================
124
+
125
+ export const TraceEntrySchema = z.object({
126
+ agentId: z.string(),
127
+ tier: AgentTierSchema,
128
+ timestamp: z.date(),
129
+ action: z.string(),
130
+ input: z.unknown().optional(),
131
+ output: z.unknown().optional(),
132
+ duration: z.number().optional(),
133
+ error: z.string().optional(),
134
+ })
135
+
136
+ export type TraceEntry = z.infer<typeof TraceEntrySchema>
137
+
138
+ // ============================================================================
139
+ // AgentCascadeContext Schema
140
+ // ============================================================================
141
+
142
+ /**
143
+ * Main cascade context schema for agent coordination
144
+ *
145
+ * Contains all information needed for agents to coordinate in a cascade:
146
+ * - Identity: Context ID and version
147
+ * - Agents: Origin and current agent references
148
+ * - Task: Task information and priority
149
+ * - State: Current execution state
150
+ * - Data: Task-specific payload
151
+ * - Trace: Execution history for debugging
152
+ * - Metadata: Extension point for custom data
153
+ */
154
+ export const AgentCascadeContextSchema = z.object({
155
+ id: z.string().min(1),
156
+ version: ContextVersionSchema,
157
+ originAgent: AgentRefSchema,
158
+ currentAgent: AgentRefSchema,
159
+ task: TaskInfoSchema,
160
+ state: ExecutionStateSchema,
161
+ data: z.record(z.unknown()).optional(),
162
+ trace: z.array(TraceEntrySchema),
163
+ metadata: z.record(z.unknown()).optional(),
164
+ })
165
+
166
+ export type AgentCascadeContext = z.infer<typeof AgentCascadeContextSchema>
167
+
168
+ // ============================================================================
169
+ // Context Enrichment Type
170
+ // ============================================================================
171
+
172
+ /**
173
+ * Data added by an agent during context propagation
174
+ */
175
+ export interface ContextEnrichment {
176
+ agentId: string
177
+ tier: AgentTier
178
+ timestamp: Date
179
+ data: Record<string, unknown>
180
+ action?: string
181
+ }
182
+
183
+ // ============================================================================
184
+ // Validation Result Type
185
+ // ============================================================================
186
+
187
+ /**
188
+ * Result of context validation
189
+ */
190
+ export interface ValidationResult {
191
+ success: boolean
192
+ data?: AgentCascadeContext
193
+ errors?: Array<{
194
+ path: (string | number)[]
195
+ message: string
196
+ code: string
197
+ }>
198
+ }
199
+
200
+ // ============================================================================
201
+ // Context Diff Types
202
+ // ============================================================================
203
+
204
+ export interface ContextChange {
205
+ path: (string | number)[]
206
+ oldValue: unknown
207
+ newValue: unknown
208
+ type: 'added' | 'removed' | 'modified'
209
+ }
210
+
211
+ export interface ContextDiff {
212
+ changes: ContextChange[]
213
+ fromVersion: ContextVersion
214
+ toVersion: ContextVersion
215
+ }
216
+
217
+ // ============================================================================
218
+ // Validation Functions
219
+ // ============================================================================
220
+
221
+ /**
222
+ * Validate a cascade context against the schema
223
+ *
224
+ * @param context - The context to validate
225
+ * @returns Validation result with success flag and errors if any
226
+ *
227
+ * @example
228
+ * ```ts
229
+ * const result = validateContext(context)
230
+ * if (!result.success) {
231
+ * console.error('Invalid context:', result.errors)
232
+ * }
233
+ * ```
234
+ */
235
+ export function validateContext(context: unknown): ValidationResult {
236
+ const result = AgentCascadeContextSchema.safeParse(context)
237
+
238
+ if (result.success) {
239
+ return { success: true, data: result.data }
240
+ }
241
+
242
+ return {
243
+ success: false,
244
+ errors: result.error.issues.map(issue => ({
245
+ path: issue.path,
246
+ message: issue.message,
247
+ code: issue.code,
248
+ })),
249
+ }
250
+ }
251
+
252
+ // ============================================================================
253
+ // Context Version Functions
254
+ // ============================================================================
255
+
256
+ /**
257
+ * Create a new context version
258
+ *
259
+ * @param options - Optional version numbers
260
+ * @returns A new ContextVersion
261
+ */
262
+ export function createContextVersion(
263
+ options?: Partial<Pick<ContextVersion, 'major' | 'minor' | 'patch'>>
264
+ ): ContextVersion {
265
+ return {
266
+ major: options?.major ?? 1,
267
+ minor: options?.minor ?? 0,
268
+ patch: options?.patch ?? 0,
269
+ timestamp: new Date(),
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Increment version patch number
275
+ */
276
+ function incrementPatch(version: ContextVersion): ContextVersion {
277
+ return {
278
+ ...version,
279
+ patch: version.patch + 1,
280
+ timestamp: new Date(),
281
+ }
282
+ }
283
+
284
+ // ============================================================================
285
+ // Context Factory Functions
286
+ // ============================================================================
287
+
288
+ /**
289
+ * Generate a unique context ID
290
+ */
291
+ function generateContextId(): string {
292
+ const timestamp = Date.now().toString(36)
293
+ const random = Math.random().toString(36).substring(2, 10)
294
+ return `ctx_${timestamp}_${random}`
295
+ }
296
+
297
+ /**
298
+ * Create a new cascade context
299
+ *
300
+ * @param options - Context creation options
301
+ * @returns A new AgentCascadeContext
302
+ *
303
+ * @example
304
+ * ```ts
305
+ * const context = createCascadeContext({
306
+ * originAgent: { id: 'coordinator', tier: 'coordinator', name: 'Main' },
307
+ * task: { id: 'task_1', type: 'analysis', priority: 'normal', description: 'Analyze data' },
308
+ * })
309
+ * ```
310
+ */
311
+ export function createCascadeContext(options: {
312
+ originAgent: AgentRef
313
+ task: TaskInfo
314
+ data?: Record<string, unknown>
315
+ metadata?: Record<string, unknown>
316
+ }): AgentCascadeContext {
317
+ return {
318
+ id: generateContextId(),
319
+ version: createContextVersion(),
320
+ originAgent: options.originAgent,
321
+ currentAgent: { ...options.originAgent },
322
+ task: options.task,
323
+ state: {
324
+ phase: 'planning',
325
+ startedAt: new Date(),
326
+ attempts: 0,
327
+ },
328
+ data: options.data,
329
+ trace: [],
330
+ metadata: options.metadata,
331
+ }
332
+ }
333
+
334
+ // ============================================================================
335
+ // Context Enrichment Functions
336
+ // ============================================================================
337
+
338
+ /**
339
+ * Enrich a context with data from an agent
340
+ *
341
+ * Creates a new context with:
342
+ * - Merged data from the enrichment
343
+ * - Updated currentAgent to the enriching agent
344
+ * - Incremented version patch
345
+ * - Added trace entry
346
+ *
347
+ * @param context - The original context
348
+ * @param enrichment - The enrichment data
349
+ * @returns A new enriched context (original is unchanged)
350
+ *
351
+ * @example
352
+ * ```ts
353
+ * const enriched = enrichContext(context, {
354
+ * agentId: 'worker_1',
355
+ * tier: 'worker',
356
+ * timestamp: new Date(),
357
+ * data: { analysisResult: 'approved' },
358
+ * })
359
+ * ```
360
+ */
361
+ export function enrichContext(
362
+ context: AgentCascadeContext,
363
+ enrichment: ContextEnrichment
364
+ ): AgentCascadeContext {
365
+ // Create trace entry
366
+ const traceEntry: TraceEntry = {
367
+ agentId: enrichment.agentId,
368
+ tier: enrichment.tier,
369
+ timestamp: enrichment.timestamp,
370
+ action: enrichment.action ?? 'enrich',
371
+ }
372
+
373
+ // Merge data deeply
374
+ const mergedData = deepMerge(context.data ?? {}, enrichment.data)
375
+
376
+ return {
377
+ ...context,
378
+ version: incrementPatch(context.version),
379
+ currentAgent: {
380
+ id: enrichment.agentId,
381
+ tier: enrichment.tier,
382
+ name: context.currentAgent.name,
383
+ },
384
+ data: mergedData,
385
+ trace: [...context.trace, traceEntry],
386
+ }
387
+ }
388
+
389
+ // ============================================================================
390
+ // Serialization Functions
391
+ // ============================================================================
392
+
393
+ /**
394
+ * Custom replacer for JSON.stringify that handles Date objects
395
+ */
396
+ function dateReplacer(_key: string, value: unknown): unknown {
397
+ if (value instanceof Date) {
398
+ return value.toISOString()
399
+ }
400
+ return value
401
+ }
402
+
403
+ /**
404
+ * Serialize a cascade context to JSON string
405
+ *
406
+ * Handles Date objects by converting them to ISO strings.
407
+ *
408
+ * @param context - The context to serialize
409
+ * @returns JSON string representation
410
+ */
411
+ export function serializeContext(context: AgentCascadeContext): string {
412
+ return JSON.stringify(context, dateReplacer)
413
+ }
414
+
415
+ /**
416
+ * Deserialize a JSON string to a cascade context
417
+ *
418
+ * Validates the deserialized data and restores Date objects.
419
+ *
420
+ * @param json - JSON string to deserialize
421
+ * @returns The deserialized and validated context
422
+ * @throws Error if JSON is invalid or context validation fails
423
+ */
424
+ export function deserializeContext(json: string): AgentCascadeContext {
425
+ let parsed: unknown
426
+ try {
427
+ parsed = JSON.parse(json)
428
+ } catch {
429
+ throw new Error('Invalid JSON string')
430
+ }
431
+
432
+ // Restore Date objects
433
+ if (typeof parsed === 'object' && parsed !== null) {
434
+ restoreDates(parsed as Record<string, unknown>)
435
+ }
436
+
437
+ // Validate the parsed context
438
+ const result = validateContext(parsed)
439
+ if (!result.success) {
440
+ throw new Error(
441
+ `Invalid context structure: ${result.errors?.map(e => e.message).join(', ')}`
442
+ )
443
+ }
444
+
445
+ return result.data!
446
+ }
447
+
448
+ /**
449
+ * Recursively restore ISO date strings to Date objects
450
+ */
451
+ function restoreDates(obj: Record<string, unknown>): void {
452
+ const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/
453
+
454
+ for (const key of Object.keys(obj)) {
455
+ const value = obj[key]
456
+ if (typeof value === 'string' && isoDateRegex.test(value)) {
457
+ obj[key] = new Date(value)
458
+ } else if (typeof value === 'object' && value !== null) {
459
+ if (Array.isArray(value)) {
460
+ for (const item of value) {
461
+ if (typeof item === 'object' && item !== null) {
462
+ restoreDates(item as Record<string, unknown>)
463
+ }
464
+ }
465
+ } else {
466
+ restoreDates(value as Record<string, unknown>)
467
+ }
468
+ }
469
+ }
470
+ }
471
+
472
+ // ============================================================================
473
+ // Context Merge/Diff Functions
474
+ // ============================================================================
475
+
476
+ /**
477
+ * Deep merge two objects
478
+ */
479
+ function deepMerge<T extends Record<string, unknown>>(
480
+ target: T,
481
+ source: Record<string, unknown>
482
+ ): T {
483
+ const result: Record<string, unknown> = { ...target }
484
+
485
+ for (const key of Object.keys(source)) {
486
+ const sourceValue = source[key]
487
+ const targetValue = result[key]
488
+
489
+ if (
490
+ typeof sourceValue === 'object' &&
491
+ sourceValue !== null &&
492
+ !Array.isArray(sourceValue) &&
493
+ typeof targetValue === 'object' &&
494
+ targetValue !== null &&
495
+ !Array.isArray(targetValue)
496
+ ) {
497
+ result[key] = deepMerge(
498
+ targetValue as Record<string, unknown>,
499
+ sourceValue as Record<string, unknown>
500
+ )
501
+ } else {
502
+ result[key] = sourceValue
503
+ }
504
+ }
505
+
506
+ return result as T
507
+ }
508
+
509
+ /**
510
+ * Merge two cascade contexts
511
+ *
512
+ * Combines contexts preferring the newer version:
513
+ * - Takes version, state from the newer context
514
+ * - Deep merges data objects
515
+ * - Concatenates trace arrays
516
+ * - Merges metadata
517
+ *
518
+ * @param ctx1 - First context
519
+ * @param ctx2 - Second context
520
+ * @returns Merged context
521
+ */
522
+ export function mergeContexts(
523
+ ctx1: AgentCascadeContext,
524
+ ctx2: AgentCascadeContext
525
+ ): AgentCascadeContext {
526
+ // Determine which is newer based on version
527
+ const ctx1Version = ctx1.version.major * 10000 + ctx1.version.minor * 100 + ctx1.version.patch
528
+ const ctx2Version = ctx2.version.major * 10000 + ctx2.version.minor * 100 + ctx2.version.patch
529
+ const [older, newer] = ctx1Version <= ctx2Version ? [ctx1, ctx2] : [ctx2, ctx1]
530
+
531
+ // Merge data deeply
532
+ const mergedData = deepMerge(older.data ?? {}, newer.data ?? {})
533
+
534
+ // Combine traces (deduplicate by timestamp + agentId)
535
+ const seenTraces = new Set<string>()
536
+ const combinedTrace: TraceEntry[] = []
537
+ for (const entry of [...older.trace, ...newer.trace]) {
538
+ const key = `${entry.agentId}-${entry.timestamp.getTime()}`
539
+ if (!seenTraces.has(key)) {
540
+ seenTraces.add(key)
541
+ combinedTrace.push(entry)
542
+ }
543
+ }
544
+
545
+ // Merge metadata
546
+ const mergedMetadata = deepMerge(older.metadata ?? {}, newer.metadata ?? {})
547
+
548
+ return {
549
+ ...newer,
550
+ data: mergedData,
551
+ trace: combinedTrace,
552
+ metadata: mergedMetadata,
553
+ }
554
+ }
555
+
556
+ /**
557
+ * Compare two contexts and return the differences
558
+ *
559
+ * @param ctx1 - Original context
560
+ * @param ctx2 - Modified context
561
+ * @returns Diff object with list of changes
562
+ */
563
+ export function diffContexts(
564
+ ctx1: AgentCascadeContext,
565
+ ctx2: AgentCascadeContext
566
+ ): ContextDiff {
567
+ const changes: ContextChange[] = []
568
+
569
+ // Helper to compare values recursively
570
+ function compareValues(
571
+ path: (string | number)[],
572
+ val1: unknown,
573
+ val2: unknown
574
+ ): void {
575
+ // Handle undefined/null
576
+ if (val1 === undefined && val2 !== undefined) {
577
+ changes.push({ path, oldValue: val1, newValue: val2, type: 'added' })
578
+ return
579
+ }
580
+ if (val1 !== undefined && val2 === undefined) {
581
+ changes.push({ path, oldValue: val1, newValue: val2, type: 'removed' })
582
+ return
583
+ }
584
+
585
+ // Handle Date objects
586
+ if (val1 instanceof Date && val2 instanceof Date) {
587
+ if (val1.getTime() !== val2.getTime()) {
588
+ changes.push({ path, oldValue: val1, newValue: val2, type: 'modified' })
589
+ }
590
+ return
591
+ }
592
+
593
+ // Handle primitives
594
+ if (typeof val1 !== 'object' || typeof val2 !== 'object') {
595
+ if (val1 !== val2) {
596
+ changes.push({ path, oldValue: val1, newValue: val2, type: 'modified' })
597
+ }
598
+ return
599
+ }
600
+
601
+ // Handle null
602
+ if (val1 === null || val2 === null) {
603
+ if (val1 !== val2) {
604
+ changes.push({ path, oldValue: val1, newValue: val2, type: 'modified' })
605
+ }
606
+ return
607
+ }
608
+
609
+ // Handle arrays
610
+ if (Array.isArray(val1) && Array.isArray(val2)) {
611
+ if (JSON.stringify(val1) !== JSON.stringify(val2)) {
612
+ changes.push({ path, oldValue: val1, newValue: val2, type: 'modified' })
613
+ }
614
+ return
615
+ }
616
+
617
+ // Handle objects
618
+ const obj1 = val1 as Record<string, unknown>
619
+ const obj2 = val2 as Record<string, unknown>
620
+ const allKeys = Array.from(new Set([...Object.keys(obj1), ...Object.keys(obj2)]))
621
+
622
+ for (const key of allKeys) {
623
+ compareValues([...path, key], obj1[key], obj2[key])
624
+ }
625
+ }
626
+
627
+ // Compare top-level fields (excluding trace which is handled specially)
628
+ const fieldsToCompare = [
629
+ 'id',
630
+ 'version',
631
+ 'originAgent',
632
+ 'currentAgent',
633
+ 'task',
634
+ 'state',
635
+ 'data',
636
+ 'metadata',
637
+ ] as const
638
+
639
+ for (const field of fieldsToCompare) {
640
+ compareValues([field], ctx1[field], ctx2[field])
641
+ }
642
+
643
+ return {
644
+ changes,
645
+ fromVersion: ctx1.version,
646
+ toVersion: ctx2.version,
647
+ }
648
+ }