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,434 @@
1
+ /**
2
+ * Tests for notify() - Notification delivery primitive
3
+ *
4
+ * The notify() function sends real notifications to Workers (Humans or AI Agents)
5
+ * via actual communication channels (Slack, email, SMS). Unlike ai-functions
6
+ * which focuses on LLM operations, this function handles real channel delivery
7
+ * with priority-based routing and delivery tracking.
8
+ *
9
+ * Note: ai-functions does not have an equivalent notify primitive since it
10
+ * focuses on LLM operations rather than communication channel delivery.
11
+ */
12
+
13
+ import { describe, it, expect } from 'vitest'
14
+ import { notify } from '../src/index.js'
15
+ import type { Worker, WorkerRef, WorkerTeam } from '../src/types.js'
16
+
17
+ // Test fixtures
18
+ const alice: Worker = {
19
+ id: 'alice',
20
+ name: 'Alice',
21
+ type: 'human',
22
+ status: 'available',
23
+ contacts: {
24
+ email: 'alice@example.com',
25
+ slack: '@alice',
26
+ sms: '+1-555-1234',
27
+ },
28
+ }
29
+
30
+ const bob: WorkerRef = {
31
+ id: 'bob',
32
+ name: 'Bob',
33
+ type: 'human',
34
+ }
35
+
36
+ const deployBot: Worker = {
37
+ id: 'deploy-bot',
38
+ name: 'Deploy Bot',
39
+ type: 'agent',
40
+ status: 'available',
41
+ contacts: {
42
+ slack: '#deploys',
43
+ webhook: 'https://hooks.example.com/deploy',
44
+ },
45
+ }
46
+
47
+ const engineering: WorkerTeam = {
48
+ id: 'eng',
49
+ name: 'Engineering',
50
+ members: [alice, bob],
51
+ contacts: {
52
+ slack: '#engineering',
53
+ email: 'eng@example.com',
54
+ },
55
+ }
56
+
57
+ describe('notify() - Notification Delivery Primitive', () => {
58
+ describe('Unit Tests (no AI)', () => {
59
+ it('should be exported from index', () => {
60
+ expect(notify).toBeDefined()
61
+ expect(typeof notify).toBe('function')
62
+ })
63
+
64
+ it('should have alert method', () => {
65
+ expect(notify.alert).toBeDefined()
66
+ expect(typeof notify.alert).toBe('function')
67
+ })
68
+
69
+ it('should have info method', () => {
70
+ expect(notify.info).toBeDefined()
71
+ expect(typeof notify.info).toBe('function')
72
+ })
73
+
74
+ it('should have rich method', () => {
75
+ expect(notify.rich).toBeDefined()
76
+ expect(typeof notify.rich).toBe('function')
77
+ })
78
+
79
+ it('should have batch method', () => {
80
+ expect(notify.batch).toBeDefined()
81
+ expect(typeof notify.batch).toBe('function')
82
+ })
83
+
84
+ it('should have schedule method', () => {
85
+ expect(notify.schedule).toBeDefined()
86
+ expect(typeof notify.schedule).toBe('function')
87
+ })
88
+ })
89
+
90
+ describe('Basic Notification Delivery', () => {
91
+ it('should send notification to worker', async () => {
92
+ const result = await notify(alice, 'Deployment completed successfully', {
93
+ via: 'slack',
94
+ })
95
+
96
+ expect(result).toBeDefined()
97
+ expect(result.sent).toBe(true)
98
+ expect(result.via).toContain('slack')
99
+ expect(result.messageId).toBeDefined()
100
+ })
101
+
102
+ it('should include recipients in result', async () => {
103
+ const result = await notify(alice, 'Hello')
104
+
105
+ expect(result.recipients).toBeDefined()
106
+ expect(Array.isArray(result.recipients)).toBe(true)
107
+ })
108
+
109
+ it('should include sentAt timestamp', async () => {
110
+ const result = await notify(alice, 'Test message', { via: 'email' })
111
+
112
+ expect(result.sentAt).toBeDefined()
113
+ expect(result.sentAt).toBeInstanceOf(Date)
114
+ })
115
+
116
+ it('should include delivery status per channel', async () => {
117
+ const result = await notify(alice, 'Message', { via: 'slack' })
118
+
119
+ expect(result.delivery).toBeDefined()
120
+ expect(Array.isArray(result.delivery)).toBe(true)
121
+ if (result.delivery && result.delivery.length > 0) {
122
+ expect(result.delivery[0].channel).toBe('slack')
123
+ expect(result.delivery[0].status).toBeDefined()
124
+ }
125
+ })
126
+
127
+ it('should handle worker without available channels', async () => {
128
+ const noChannelWorker: Worker = {
129
+ id: 'no-channel',
130
+ name: 'No Channel',
131
+ type: 'human',
132
+ status: 'available',
133
+ contacts: {},
134
+ }
135
+
136
+ const result = await notify(noChannelWorker, 'Hello')
137
+
138
+ expect(result.sent).toBe(false)
139
+ expect(result.via).toHaveLength(0)
140
+ })
141
+
142
+ it('should handle string target without contacts', async () => {
143
+ // String targets don't have contacts configured, so sent should be false
144
+ const result = await notify('user-id', 'Message')
145
+
146
+ expect(result).toBeDefined()
147
+ expect(result.sent).toBe(false)
148
+ expect(result.via).toHaveLength(0)
149
+ })
150
+ })
151
+
152
+ describe('Channel Selection', () => {
153
+ it('should use specified channel', async () => {
154
+ const result = await notify(alice, 'Message', { via: 'email' })
155
+
156
+ expect(result.via).toContain('email')
157
+ })
158
+
159
+ it('should fallback to first available channel', async () => {
160
+ const result = await notify(alice, 'Message')
161
+
162
+ // Should use first available (email, slack, or sms)
163
+ expect(result.via.length).toBeGreaterThan(0)
164
+ })
165
+
166
+ it('should handle multiple channels', async () => {
167
+ const result = await notify(alice, 'Urgent message', {
168
+ via: ['slack', 'email'],
169
+ })
170
+
171
+ expect(result.via).toContain('slack')
172
+ })
173
+
174
+ it('should only use available channels from array', async () => {
175
+ const slackOnlyWorker: Worker = {
176
+ id: 'slack-only',
177
+ name: 'Slack Only',
178
+ type: 'human',
179
+ status: 'available',
180
+ contacts: {
181
+ slack: '@user',
182
+ },
183
+ }
184
+
185
+ const result = await notify(slackOnlyWorker, 'Message', {
186
+ via: ['email', 'slack'], // email not available
187
+ })
188
+
189
+ expect(result.via).toContain('slack')
190
+ expect(result.via).not.toContain('email')
191
+ })
192
+ })
193
+
194
+ describe('Priority-based Delivery', () => {
195
+ it('should support normal priority', async () => {
196
+ const result = await notify(alice, 'Normal message', {
197
+ via: 'slack',
198
+ priority: 'normal',
199
+ })
200
+
201
+ expect(result.sent).toBe(true)
202
+ })
203
+
204
+ it('should support urgent priority with multiple channels', async () => {
205
+ const result = await notify(alice, 'URGENT: Server down!', {
206
+ priority: 'urgent',
207
+ })
208
+
209
+ // Urgent should try multiple channels
210
+ expect(result).toBeDefined()
211
+ })
212
+
213
+ it('should support low priority', async () => {
214
+ const result = await notify(alice, 'FYI message', {
215
+ via: 'email',
216
+ priority: 'low',
217
+ })
218
+
219
+ expect(result.sent).toBe(true)
220
+ })
221
+
222
+ it('should support high priority', async () => {
223
+ const result = await notify(alice, 'Important message', {
224
+ via: 'slack',
225
+ priority: 'high',
226
+ })
227
+
228
+ expect(result.sent).toBe(true)
229
+ })
230
+ })
231
+
232
+ describe('Alert Notifications', () => {
233
+ it('should send alert with urgent priority', async () => {
234
+ const result = await notify.alert(alice, 'Production is down!')
235
+
236
+ expect(result).toBeDefined()
237
+ // Alert implicitly sets priority to urgent
238
+ })
239
+
240
+ it('should allow custom options with alert', async () => {
241
+ const result = await notify.alert(alice, 'Critical error', {
242
+ via: 'sms',
243
+ })
244
+
245
+ expect(result.via).toContain('sms')
246
+ })
247
+ })
248
+
249
+ describe('Info Notifications', () => {
250
+ it('should send info with low priority', async () => {
251
+ const result = await notify.info(alice, 'Weekly sync notes posted')
252
+
253
+ expect(result).toBeDefined()
254
+ // Info implicitly sets priority to low
255
+ })
256
+
257
+ it('should allow custom channel for info', async () => {
258
+ const result = await notify.info(alice, 'Update available', {
259
+ via: 'email',
260
+ })
261
+
262
+ expect(result.via).toContain('email')
263
+ })
264
+ })
265
+
266
+ describe('Rich Notifications', () => {
267
+ it('should send rich notification with title and body', async () => {
268
+ const result = await notify.rich(
269
+ alice,
270
+ 'Deployment Complete',
271
+ 'Version 2.1.0 has been deployed to production.',
272
+ { via: 'slack' }
273
+ )
274
+
275
+ expect(result).toBeDefined()
276
+ expect(result.sent).toBe(true)
277
+ })
278
+
279
+ it('should support metadata in rich notifications', async () => {
280
+ const result = await notify.rich(alice, 'Build Status', 'Build #123 completed successfully', {
281
+ via: 'slack',
282
+ metadata: {
283
+ buildNumber: 123,
284
+ duration: '5m 32s',
285
+ },
286
+ })
287
+
288
+ expect(result.sent).toBe(true)
289
+ })
290
+ })
291
+
292
+ describe('Batch Notifications', () => {
293
+ it('should send multiple notifications', async () => {
294
+ const results = await notify.batch([
295
+ { target: alice, message: 'Task 1 complete' },
296
+ { target: engineering, message: 'All tasks done' },
297
+ ])
298
+
299
+ expect(results).toBeDefined()
300
+ expect(Array.isArray(results)).toBe(true)
301
+ expect(results.length).toBe(2)
302
+ })
303
+
304
+ it('should support individual options per notification', async () => {
305
+ const results = await notify.batch([
306
+ { target: alice, message: 'Urgent', options: { priority: 'urgent' } },
307
+ { target: alice, message: 'Normal', options: { priority: 'normal' } },
308
+ ])
309
+
310
+ expect(results.length).toBe(2)
311
+ })
312
+
313
+ it('should handle mixed targets in batch', async () => {
314
+ const results = await notify.batch([
315
+ { target: alice, message: 'Personal message' },
316
+ { target: 'user-id', message: 'ID-based message' },
317
+ { target: engineering, message: 'Team message' },
318
+ ])
319
+
320
+ expect(results.length).toBe(3)
321
+ })
322
+ })
323
+
324
+ describe('Scheduled Notifications', () => {
325
+ it('should schedule notification with Date', async () => {
326
+ const futureDate = new Date(Date.now() + 60000) // 1 minute from now
327
+ const result = await notify.schedule(alice, 'Reminder', futureDate)
328
+
329
+ expect(result).toBeDefined()
330
+ expect(result.scheduled).toBe(true)
331
+ expect(result.scheduledFor).toEqual(futureDate)
332
+ expect(result.messageId).toBeDefined()
333
+ })
334
+
335
+ it('should schedule notification with delay in ms', async () => {
336
+ const result = await notify.schedule(alice, 'Delayed message', 30000)
337
+
338
+ expect(result.scheduled).toBe(true)
339
+ expect(result.scheduledFor).toBeInstanceOf(Date)
340
+ // Should be approximately 30 seconds from now
341
+ const expectedTime = Date.now() + 30000
342
+ expect(result.scheduledFor.getTime()).toBeCloseTo(expectedTime, -2)
343
+ })
344
+
345
+ it('should support options with scheduled notifications', async () => {
346
+ const result = await notify.schedule(alice, 'Scheduled alert', 60000, { priority: 'high' })
347
+
348
+ expect(result.scheduled).toBe(true)
349
+ })
350
+
351
+ it('should include messageId for scheduled notifications', async () => {
352
+ const result = await notify.schedule(alice, 'Scheduled', 1000)
353
+
354
+ expect(result.messageId).toBeDefined()
355
+ expect(result.messageId.startsWith('scheduled')).toBe(true)
356
+ })
357
+ })
358
+
359
+ describe('Team Notifications', () => {
360
+ it('should notify entire team', async () => {
361
+ const result = await notify(engineering, 'Team update', {
362
+ via: 'slack',
363
+ })
364
+
365
+ expect(result).toBeDefined()
366
+ expect(result.recipients).toBeDefined()
367
+ // Should include all team members
368
+ })
369
+
370
+ it('should use team contacts', async () => {
371
+ const result = await notify(engineering, 'Sprint planning tomorrow', {
372
+ via: 'slack',
373
+ })
374
+
375
+ expect(result.via).toContain('slack')
376
+ })
377
+ })
378
+
379
+ describe('Agent Notifications', () => {
380
+ it('should notify AI agent', async () => {
381
+ const result = await notify(deployBot, 'New deployment requested', {
382
+ via: 'slack',
383
+ })
384
+
385
+ expect(result).toBeDefined()
386
+ expect(result.sent).toBe(true)
387
+ })
388
+
389
+ it('should support webhook channel for agents', async () => {
390
+ const result = await notify(deployBot, 'Trigger deployment', {
391
+ via: 'webhook',
392
+ })
393
+
394
+ expect(result.via).toContain('webhook')
395
+ })
396
+ })
397
+
398
+ describe('Metadata Support', () => {
399
+ it('should include custom metadata', async () => {
400
+ const result = await notify(alice, 'Build complete', {
401
+ via: 'slack',
402
+ metadata: {
403
+ buildId: '12345',
404
+ duration: 120,
405
+ status: 'success',
406
+ },
407
+ })
408
+
409
+ expect(result).toBeDefined()
410
+ expect(result.sent).toBe(true)
411
+ })
412
+ })
413
+
414
+ describe('Delivery Tracking', () => {
415
+ it('should track delivery per channel', async () => {
416
+ const result = await notify(alice, 'Tracked message', {
417
+ via: 'slack',
418
+ })
419
+
420
+ expect(result.delivery).toBeDefined()
421
+ expect(Array.isArray(result.delivery)).toBe(true)
422
+ })
423
+
424
+ it('should report failed deliveries', async () => {
425
+ // Worker with channel that doesn't exist
426
+ const result = await notify(alice, 'Message', {
427
+ via: 'teams', // Alice doesn't have teams configured
428
+ })
429
+
430
+ // Should not include teams in via since it's not available
431
+ expect(result.via).not.toContain('teams')
432
+ })
433
+ })
434
+ })