digital-workers 0.1.1 → 2.0.1

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 (83) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/CHANGELOG.md +9 -0
  3. package/README.md +290 -106
  4. package/dist/actions.d.ts +95 -0
  5. package/dist/actions.d.ts.map +1 -0
  6. package/dist/actions.js +437 -0
  7. package/dist/actions.js.map +1 -0
  8. package/dist/approve.d.ts +49 -0
  9. package/dist/approve.d.ts.map +1 -0
  10. package/dist/approve.js +235 -0
  11. package/dist/approve.js.map +1 -0
  12. package/dist/ask.d.ts +42 -0
  13. package/dist/ask.d.ts.map +1 -0
  14. package/dist/ask.js +227 -0
  15. package/dist/ask.js.map +1 -0
  16. package/dist/decide.d.ts +62 -0
  17. package/dist/decide.d.ts.map +1 -0
  18. package/dist/decide.js +245 -0
  19. package/dist/decide.js.map +1 -0
  20. package/dist/do.d.ts +63 -0
  21. package/dist/do.d.ts.map +1 -0
  22. package/dist/do.js +228 -0
  23. package/dist/do.js.map +1 -0
  24. package/dist/generate.d.ts +61 -0
  25. package/dist/generate.d.ts.map +1 -0
  26. package/dist/generate.js +299 -0
  27. package/dist/generate.js.map +1 -0
  28. package/dist/goals.d.ts +89 -0
  29. package/dist/goals.d.ts.map +1 -0
  30. package/dist/goals.js +206 -0
  31. package/dist/goals.js.map +1 -0
  32. package/dist/index.d.ts +68 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +69 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/is.d.ts +54 -0
  37. package/dist/is.d.ts.map +1 -0
  38. package/dist/is.js +318 -0
  39. package/dist/is.js.map +1 -0
  40. package/dist/kpis.d.ts +103 -0
  41. package/dist/kpis.d.ts.map +1 -0
  42. package/dist/kpis.js +271 -0
  43. package/dist/kpis.js.map +1 -0
  44. package/dist/notify.d.ts +47 -0
  45. package/dist/notify.d.ts.map +1 -0
  46. package/dist/notify.js +220 -0
  47. package/dist/notify.js.map +1 -0
  48. package/dist/role.d.ts +53 -0
  49. package/dist/role.d.ts.map +1 -0
  50. package/dist/role.js +111 -0
  51. package/dist/role.js.map +1 -0
  52. package/dist/team.d.ts +61 -0
  53. package/dist/team.d.ts.map +1 -0
  54. package/dist/team.js +131 -0
  55. package/dist/team.js.map +1 -0
  56. package/dist/transports.d.ts +164 -0
  57. package/dist/transports.d.ts.map +1 -0
  58. package/dist/transports.js +358 -0
  59. package/dist/transports.js.map +1 -0
  60. package/dist/types.d.ts +693 -0
  61. package/dist/types.d.ts.map +1 -0
  62. package/dist/types.js +72 -0
  63. package/dist/types.js.map +1 -0
  64. package/package.json +27 -61
  65. package/src/actions.ts +615 -0
  66. package/src/approve.ts +317 -0
  67. package/src/ask.ts +304 -0
  68. package/src/decide.ts +295 -0
  69. package/src/do.ts +275 -0
  70. package/src/generate.ts +364 -0
  71. package/src/goals.ts +220 -0
  72. package/src/index.ts +118 -0
  73. package/src/is.ts +372 -0
  74. package/src/kpis.ts +348 -0
  75. package/src/notify.ts +303 -0
  76. package/src/role.ts +116 -0
  77. package/src/team.ts +142 -0
  78. package/src/transports.ts +504 -0
  79. package/src/types.ts +843 -0
  80. package/test/actions.test.ts +546 -0
  81. package/test/standalone.test.ts +299 -0
  82. package/test/types.test.ts +460 -0
  83. package/tsconfig.json +9 -0
package/src/actions.ts ADDED
@@ -0,0 +1,615 @@
1
+ /**
2
+ * Worker Actions - Workflow Integration
3
+ *
4
+ * Worker actions (notify, ask, approve, decide, do) are durable workflow actions
5
+ * that integrate with ai-workflows. They can be invoked via:
6
+ *
7
+ * 1. `$.do('Worker.notify', data)` - Durable action
8
+ * 2. `$.send('Worker.notify', data)` - Fire and forget
9
+ * 3. `$.notify(target, message)` - Convenience method (when using withWorkers)
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+
14
+ import type { WorkflowContext } from 'ai-workflows'
15
+ import type {
16
+ Worker,
17
+ Team,
18
+ WorkerRef,
19
+ ActionTarget,
20
+ ContactChannel,
21
+ Contacts,
22
+ NotifyActionData,
23
+ AskActionData,
24
+ ApproveActionData,
25
+ DecideActionData,
26
+ DoActionData,
27
+ NotifyResult,
28
+ AskResult,
29
+ ApprovalResult,
30
+ DecideResult,
31
+ DoResult,
32
+ NotifyOptions,
33
+ AskOptions,
34
+ ApproveOptions,
35
+ DecideOptions,
36
+ WorkerContext,
37
+ } from './types.js'
38
+
39
+ // ============================================================================
40
+ // Action Handlers
41
+ // ============================================================================
42
+
43
+ /**
44
+ * Handle Worker.notify action
45
+ */
46
+ export async function handleNotify(
47
+ data: NotifyActionData,
48
+ $: WorkflowContext
49
+ ): Promise<NotifyResult> {
50
+ const { object: target, message, via, priority = 'normal' } = data
51
+
52
+ // Resolve target to get contacts
53
+ const { contacts, recipients } = resolveTarget(target)
54
+
55
+ // Determine which channels to use
56
+ const channels = resolveChannels(via, contacts, priority)
57
+
58
+ if (channels.length === 0) {
59
+ return {
60
+ sent: false,
61
+ via: [],
62
+ sentAt: new Date(),
63
+ messageId: generateId('msg'),
64
+ }
65
+ }
66
+
67
+ // Send to each channel
68
+ const delivery = await Promise.all(
69
+ channels.map(async (channel) => {
70
+ try {
71
+ await sendToChannel(channel, message, contacts, { priority })
72
+ return { channel, status: 'sent' as const }
73
+ } catch (error) {
74
+ return {
75
+ channel,
76
+ status: 'failed' as const,
77
+ error: error instanceof Error ? error.message : 'Unknown error',
78
+ }
79
+ }
80
+ })
81
+ )
82
+
83
+ const sent = delivery.some((d) => d.status === 'sent')
84
+ const result: NotifyResult = {
85
+ sent,
86
+ via: channels,
87
+ recipients,
88
+ sentAt: new Date(),
89
+ messageId: generateId('msg'),
90
+ delivery,
91
+ }
92
+
93
+ // Emit result event
94
+ if (sent) {
95
+ await $.send('Worker.notified', { ...data, result })
96
+ }
97
+
98
+ return result
99
+ }
100
+
101
+ /**
102
+ * Handle Worker.ask action
103
+ */
104
+ export async function handleAsk<T = string>(
105
+ data: AskActionData,
106
+ $: WorkflowContext
107
+ ): Promise<AskResult<T>> {
108
+ const { object: target, question, via, schema, timeout } = data
109
+
110
+ // Resolve target
111
+ const { contacts, recipients } = resolveTarget(target)
112
+ const recipient = recipients[0]
113
+
114
+ // Determine channel
115
+ const channel = resolveChannel(via, contacts)
116
+
117
+ if (!channel) {
118
+ throw new Error('No valid channel available for ask action')
119
+ }
120
+
121
+ // Send question and wait for response
122
+ const answer = await sendQuestion<T>(channel, question, contacts, { schema, timeout })
123
+
124
+ const result: AskResult<T> = {
125
+ answer,
126
+ answeredBy: recipient,
127
+ answeredAt: new Date(),
128
+ via: channel,
129
+ }
130
+
131
+ // Emit result event
132
+ await $.send('Worker.answered', { ...data, result })
133
+
134
+ return result
135
+ }
136
+
137
+ /**
138
+ * Handle Worker.approve action
139
+ */
140
+ export async function handleApprove(
141
+ data: ApproveActionData,
142
+ $: WorkflowContext
143
+ ): Promise<ApprovalResult> {
144
+ const { object: target, request, via, context, timeout, escalate } = data
145
+
146
+ // Resolve target
147
+ const { contacts, recipients } = resolveTarget(target)
148
+ const approver = recipients[0]
149
+
150
+ // Determine channel
151
+ const channel = resolveChannel(via, contacts)
152
+
153
+ if (!channel) {
154
+ throw new Error('No valid channel available for approve action')
155
+ }
156
+
157
+ // Send approval request and wait for response
158
+ const response = await sendApprovalRequest(channel, request, contacts, {
159
+ context,
160
+ timeout,
161
+ escalate,
162
+ })
163
+
164
+ const result: ApprovalResult = {
165
+ approved: response.approved,
166
+ approvedBy: approver,
167
+ approvedAt: new Date(),
168
+ notes: response.notes,
169
+ via: channel,
170
+ }
171
+
172
+ // Emit result event
173
+ await $.send(result.approved ? 'Worker.approved' : 'Worker.rejected', { ...data, result })
174
+
175
+ return result
176
+ }
177
+
178
+ /**
179
+ * Handle Worker.decide action
180
+ */
181
+ export async function handleDecide<T = string>(
182
+ data: DecideActionData,
183
+ $: WorkflowContext
184
+ ): Promise<DecideResult<T>> {
185
+ const { options, context, criteria } = data
186
+
187
+ // Use AI to make decision
188
+ const result = await makeDecision<T>(options as T[], context, criteria)
189
+
190
+ // Emit result event
191
+ await $.send('Worker.decided', { ...data, result })
192
+
193
+ return result
194
+ }
195
+
196
+ /**
197
+ * Handle Worker.do action
198
+ */
199
+ export async function handleDo<T = unknown>(
200
+ data: DoActionData,
201
+ $: WorkflowContext
202
+ ): Promise<DoResult<T>> {
203
+ const { object: target, instruction, timeout, maxRetries = 3 } = data
204
+
205
+ const startTime = Date.now()
206
+ const steps: DoResult<T>['steps'] = []
207
+
208
+ let lastError: Error | undefined
209
+ let result: T | undefined
210
+
211
+ // Retry loop
212
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
213
+ try {
214
+ steps.push({
215
+ action: attempt === 0 ? 'start' : `retry_${attempt}`,
216
+ result: { instruction },
217
+ timestamp: new Date(),
218
+ })
219
+
220
+ // Execute the task (this would integrate with agent execution)
221
+ result = await executeTask<T>(target, instruction, { timeout })
222
+
223
+ steps.push({
224
+ action: 'complete',
225
+ result,
226
+ timestamp: new Date(),
227
+ })
228
+
229
+ const doResult: DoResult<T> = {
230
+ result: result as T,
231
+ success: true,
232
+ duration: Date.now() - startTime,
233
+ steps,
234
+ }
235
+
236
+ await $.send('Worker.done', { ...data, result: doResult })
237
+ return doResult
238
+ } catch (error) {
239
+ lastError = error instanceof Error ? error : new Error(String(error))
240
+ steps.push({
241
+ action: 'error',
242
+ result: { error: lastError.message, attempt },
243
+ timestamp: new Date(),
244
+ })
245
+ }
246
+ }
247
+
248
+ // All retries failed
249
+ const failResult: DoResult<T> = {
250
+ result: undefined as T,
251
+ success: false,
252
+ error: lastError?.message,
253
+ duration: Date.now() - startTime,
254
+ steps,
255
+ }
256
+
257
+ await $.send('Worker.failed', { ...data, result: failResult })
258
+ return failResult
259
+ }
260
+
261
+ // ============================================================================
262
+ // Workflow Extension
263
+ // ============================================================================
264
+
265
+ /**
266
+ * Register Worker action handlers with a workflow
267
+ *
268
+ * @example
269
+ * ```ts
270
+ * import { Workflow } from 'ai-workflows'
271
+ * import { registerWorkerActions } from 'digital-workers'
272
+ *
273
+ * const workflow = Workflow($ => {
274
+ * registerWorkerActions($)
275
+ *
276
+ * $.on.Expense.submitted(async (expense, $) => {
277
+ * const approval = await $.do('Worker.approve', {
278
+ * actor: 'system',
279
+ * object: manager,
280
+ * request: `Expense: $${expense.amount}`,
281
+ * via: 'slack',
282
+ * })
283
+ * })
284
+ * })
285
+ * ```
286
+ */
287
+ export function registerWorkerActions($: WorkflowContext): void {
288
+ // Register action handlers using the proxy pattern
289
+ // The $ context provides event registration via $.on[Namespace][event]
290
+ const on = $.on as Record<string, Record<string, (handler: unknown) => void>>
291
+
292
+ if (on.Worker) {
293
+ on.Worker.notify?.(handleNotify)
294
+ on.Worker.ask?.(handleAsk)
295
+ on.Worker.approve?.(handleApprove)
296
+ on.Worker.decide?.(handleDecide)
297
+ on.Worker.do?.(handleDo)
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Extend WorkflowContext with convenience methods for worker actions
303
+ *
304
+ * @example
305
+ * ```ts
306
+ * const workflow = Workflow($ => {
307
+ * const worker$ = withWorkers($)
308
+ *
309
+ * $.on.Expense.submitted(async (expense) => {
310
+ * await worker$.notify(finance, `New expense: ${expense.amount}`)
311
+ *
312
+ * const approval = await worker$.approve(
313
+ * `Expense: $${expense.amount}`,
314
+ * manager,
315
+ * { via: 'slack' }
316
+ * )
317
+ * })
318
+ * })
319
+ * ```
320
+ */
321
+ export function withWorkers($: WorkflowContext): WorkflowContext & WorkerContext {
322
+ const workerContext: WorkerContext = {
323
+ async notify(
324
+ target: ActionTarget,
325
+ message: string,
326
+ options: NotifyOptions = {}
327
+ ): Promise<NotifyResult> {
328
+ return $.do<NotifyActionData, NotifyResult>('Worker.notify', {
329
+ actor: 'system',
330
+ object: target,
331
+ action: 'notify',
332
+ message,
333
+ ...options,
334
+ })
335
+ },
336
+
337
+ async ask<T = string>(
338
+ target: ActionTarget,
339
+ question: string,
340
+ options: AskOptions = {}
341
+ ): Promise<AskResult<T>> {
342
+ return $.do<AskActionData, AskResult<T>>('Worker.ask', {
343
+ actor: 'system',
344
+ object: target,
345
+ action: 'ask',
346
+ question,
347
+ ...options,
348
+ })
349
+ },
350
+
351
+ async approve(
352
+ request: string,
353
+ target: ActionTarget,
354
+ options: ApproveOptions = {}
355
+ ): Promise<ApprovalResult> {
356
+ // Convert ActionTarget to a suitable actor reference
357
+ const actor: string | WorkerRef = typeof target === 'string'
358
+ ? target
359
+ : 'id' in target
360
+ ? { id: target.id, type: 'type' in target ? target.type : undefined, name: 'name' in target ? target.name : undefined }
361
+ : 'system'
362
+
363
+ return $.do<ApproveActionData, ApprovalResult>('Worker.approve', {
364
+ actor,
365
+ object: target,
366
+ action: 'approve',
367
+ request,
368
+ ...options,
369
+ })
370
+ },
371
+
372
+ async decide<T = string>(
373
+ options: DecideOptions<T>
374
+ ): Promise<DecideResult<T>> {
375
+ return $.do<DecideActionData, DecideResult<T>>('Worker.decide', {
376
+ actor: 'ai',
377
+ object: 'decision',
378
+ action: 'decide',
379
+ ...options,
380
+ })
381
+ },
382
+ }
383
+
384
+ return { ...$, ...workerContext }
385
+ }
386
+
387
+ // ============================================================================
388
+ // Standalone Functions (for use outside workflows)
389
+ // ============================================================================
390
+
391
+ /**
392
+ * Send a notification (standalone, non-durable)
393
+ */
394
+ export async function notify(
395
+ target: ActionTarget,
396
+ message: string,
397
+ options: NotifyOptions = {}
398
+ ): Promise<NotifyResult> {
399
+ const { contacts, recipients } = resolveTarget(target)
400
+ const channels = resolveChannels(options.via, contacts, options.priority || 'normal')
401
+
402
+ if (channels.length === 0) {
403
+ return { sent: false, via: [], messageId: generateId('msg') }
404
+ }
405
+
406
+ const delivery = await Promise.all(
407
+ channels.map(async (channel) => {
408
+ try {
409
+ await sendToChannel(channel, message, contacts, { priority: options.priority })
410
+ return { channel, status: 'sent' as const }
411
+ } catch (error) {
412
+ return { channel, status: 'failed' as const, error: String(error) }
413
+ }
414
+ })
415
+ )
416
+
417
+ return {
418
+ sent: delivery.some((d) => d.status === 'sent'),
419
+ via: channels,
420
+ recipients,
421
+ sentAt: new Date(),
422
+ messageId: generateId('msg'),
423
+ delivery,
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Ask a question (standalone, non-durable)
429
+ */
430
+ export async function ask<T = string>(
431
+ target: ActionTarget,
432
+ question: string,
433
+ options: AskOptions = {}
434
+ ): Promise<AskResult<T>> {
435
+ const { contacts, recipients } = resolveTarget(target)
436
+ const channel = resolveChannel(options.via, contacts)
437
+
438
+ if (!channel) {
439
+ throw new Error('No valid channel available')
440
+ }
441
+
442
+ const answer = await sendQuestion<T>(channel, question, contacts, options)
443
+
444
+ return {
445
+ answer,
446
+ answeredBy: recipients[0],
447
+ answeredAt: new Date(),
448
+ via: channel,
449
+ }
450
+ }
451
+
452
+ /**
453
+ * Request approval (standalone, non-durable)
454
+ */
455
+ export async function approve(
456
+ request: string,
457
+ target: ActionTarget,
458
+ options: ApproveOptions = {}
459
+ ): Promise<ApprovalResult> {
460
+ const { contacts, recipients } = resolveTarget(target)
461
+ const channel = resolveChannel(options.via, contacts)
462
+
463
+ if (!channel) {
464
+ throw new Error('No valid channel available')
465
+ }
466
+
467
+ const response = await sendApprovalRequest(channel, request, contacts, options)
468
+
469
+ return {
470
+ approved: response.approved,
471
+ approvedBy: recipients[0],
472
+ approvedAt: new Date(),
473
+ notes: response.notes,
474
+ via: channel,
475
+ }
476
+ }
477
+
478
+ /**
479
+ * Make a decision (standalone, non-durable)
480
+ */
481
+ export async function decide<T = string>(
482
+ options: DecideOptions<T>
483
+ ): Promise<DecideResult<T>> {
484
+ return makeDecision(options.options, options.context, options.criteria)
485
+ }
486
+
487
+ // ============================================================================
488
+ // Internal Helpers
489
+ // ============================================================================
490
+
491
+ function resolveTarget(target: ActionTarget): {
492
+ contacts: Contacts
493
+ recipients: WorkerRef[]
494
+ } {
495
+ if (typeof target === 'string') {
496
+ return { contacts: {}, recipients: [{ id: target }] }
497
+ }
498
+
499
+ if ('contacts' in target) {
500
+ const recipients: WorkerRef[] =
501
+ 'members' in target
502
+ ? target.members
503
+ : [{ id: target.id, type: target.type, name: target.name }]
504
+ return { contacts: target.contacts, recipients }
505
+ }
506
+
507
+ return { contacts: {}, recipients: [target] }
508
+ }
509
+
510
+ function resolveChannels(
511
+ via: ContactChannel | ContactChannel[] | undefined,
512
+ contacts: Contacts,
513
+ priority: string
514
+ ): ContactChannel[] {
515
+ if (via) {
516
+ const requested = Array.isArray(via) ? via : [via]
517
+ return requested.filter((c) => contacts[c] !== undefined)
518
+ }
519
+
520
+ const available = Object.keys(contacts) as ContactChannel[]
521
+ if (available.length === 0) return []
522
+
523
+ const firstChannel = available[0]
524
+ if (!firstChannel) return []
525
+
526
+ if (priority === 'urgent') {
527
+ const urgentChannels: ContactChannel[] = ['slack', 'sms', 'phone']
528
+ const urgent = available.filter((c) => urgentChannels.includes(c))
529
+ return urgent.length > 0 ? urgent : [firstChannel]
530
+ }
531
+
532
+ return [firstChannel]
533
+ }
534
+
535
+ function resolveChannel(
536
+ via: ContactChannel | ContactChannel[] | undefined,
537
+ contacts: Contacts
538
+ ): ContactChannel | null {
539
+ if (via) {
540
+ const channel = Array.isArray(via) ? via[0] : via
541
+ if (channel && contacts[channel] !== undefined) return channel
542
+ }
543
+ const available = Object.keys(contacts) as ContactChannel[]
544
+ const first = available[0]
545
+ return first ?? null
546
+ }
547
+
548
+ async function sendToChannel(
549
+ channel: ContactChannel,
550
+ message: string,
551
+ contacts: Contacts,
552
+ options: { priority?: string }
553
+ ): Promise<void> {
554
+ // In a real implementation, this would send via Slack API, SendGrid, Twilio, etc.
555
+ await new Promise((resolve) => setTimeout(resolve, 10))
556
+ }
557
+
558
+ async function sendQuestion<T>(
559
+ channel: ContactChannel,
560
+ question: string,
561
+ contacts: Contacts,
562
+ options: { schema?: unknown; timeout?: number }
563
+ ): Promise<T> {
564
+ // In a real implementation, this would send the question and wait for response
565
+ await new Promise((resolve) => setTimeout(resolve, 10))
566
+ return 'Pending response...' as T
567
+ }
568
+
569
+ async function sendApprovalRequest(
570
+ channel: ContactChannel,
571
+ request: string,
572
+ contacts: Contacts,
573
+ options: { context?: unknown; timeout?: number; escalate?: boolean }
574
+ ): Promise<{ approved: boolean; notes?: string }> {
575
+ // In a real implementation, this would send approval request and wait
576
+ await new Promise((resolve) => setTimeout(resolve, 10))
577
+ return { approved: false, notes: 'Pending approval...' }
578
+ }
579
+
580
+ async function makeDecision<T>(
581
+ options: T[],
582
+ context?: string | Record<string, unknown>,
583
+ criteria?: string[]
584
+ ): Promise<DecideResult<T>> {
585
+ if (options.length === 0) {
586
+ throw new Error('At least one option is required for a decision')
587
+ }
588
+
589
+ // In a real implementation, this would use AI to make a decision
590
+ // For now, return first option with mock data
591
+ const choice = options[0] as T
592
+ return {
593
+ choice,
594
+ reasoning: 'Decision pending...',
595
+ confidence: 0.5,
596
+ alternatives: options.slice(1).map((opt, i) => ({
597
+ option: opt,
598
+ score: 50 - i * 10,
599
+ })),
600
+ }
601
+ }
602
+
603
+ async function executeTask<T>(
604
+ target: ActionTarget,
605
+ instruction: string,
606
+ options: { timeout?: number }
607
+ ): Promise<T> {
608
+ // In a real implementation, this would execute the task via the target worker
609
+ await new Promise((resolve) => setTimeout(resolve, 10))
610
+ return { completed: true, instruction } as T
611
+ }
612
+
613
+ function generateId(prefix: string): string {
614
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
615
+ }