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/notify.ts ADDED
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Notification functionality for digital workers
3
+ */
4
+
5
+ import type {
6
+ Worker,
7
+ Team,
8
+ WorkerRef,
9
+ ActionTarget,
10
+ ContactChannel,
11
+ NotifyResult,
12
+ NotifyOptions,
13
+ Contacts,
14
+ } from './types.js'
15
+
16
+ /**
17
+ * Send a notification to a worker or team
18
+ *
19
+ * Routes notifications through the specified channel(s), falling back
20
+ * to the target's preferred channel if not specified.
21
+ *
22
+ * @param target - The worker or team to notify
23
+ * @param message - The notification message
24
+ * @param options - Notification options
25
+ * @returns Promise resolving to notification result
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * // Notify a worker via their preferred channel
30
+ * await notify(alice, 'Deployment completed successfully')
31
+ *
32
+ * // Notify via specific channel
33
+ * await notify(alice, 'Urgent: Server down!', { via: 'slack' })
34
+ *
35
+ * // Notify via multiple channels
36
+ * await notify(alice, 'Critical alert', { via: ['slack', 'sms'] })
37
+ *
38
+ * // Notify a team
39
+ * await notify(engineering, 'Sprint planning tomorrow', { via: 'slack' })
40
+ * ```
41
+ */
42
+ export async function notify(
43
+ target: ActionTarget,
44
+ message: string,
45
+ options: NotifyOptions = {}
46
+ ): Promise<NotifyResult> {
47
+ const { via, priority = 'normal', fallback = false, timeout, context, metadata } = options
48
+
49
+ // Resolve target to get contacts
50
+ const { contacts, recipients } = resolveTarget(target)
51
+
52
+ // Determine which channels to use
53
+ const channels = resolveChannels(via, contacts, priority)
54
+
55
+ if (channels.length === 0) {
56
+ return {
57
+ sent: false,
58
+ via: [],
59
+ sentAt: new Date(),
60
+ messageId: generateMessageId(),
61
+ delivery: [],
62
+ }
63
+ }
64
+
65
+ // Send to each channel
66
+ const delivery = await Promise.all(
67
+ channels.map(async (channel) => {
68
+ try {
69
+ await sendToChannel(channel, message, contacts, { priority, metadata })
70
+ return { channel, status: 'sent' as const }
71
+ } catch (error) {
72
+ return {
73
+ channel,
74
+ status: 'failed' as const,
75
+ error: error instanceof Error ? error.message : 'Unknown error',
76
+ }
77
+ }
78
+ })
79
+ )
80
+
81
+ const sent = delivery.some((d) => d.status === 'sent')
82
+
83
+ return {
84
+ sent,
85
+ via: channels,
86
+ recipients,
87
+ sentAt: new Date(),
88
+ messageId: generateMessageId(),
89
+ delivery,
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Send a high-priority alert notification
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * await notify.alert(oncallEngineer, 'Production is down!')
99
+ * ```
100
+ */
101
+ notify.alert = async (
102
+ target: ActionTarget,
103
+ message: string,
104
+ options: NotifyOptions = {}
105
+ ): Promise<NotifyResult> => {
106
+ return notify(target, message, { ...options, priority: 'urgent' })
107
+ }
108
+
109
+ /**
110
+ * Send a low-priority info notification
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * await notify.info(team, 'Weekly sync notes posted')
115
+ * ```
116
+ */
117
+ notify.info = async (
118
+ target: ActionTarget,
119
+ message: string,
120
+ options: NotifyOptions = {}
121
+ ): Promise<NotifyResult> => {
122
+ return notify(target, message, { ...options, priority: 'low' })
123
+ }
124
+
125
+ /**
126
+ * Send a rich notification with title and body
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * await notify.rich(alice, 'Deployment Complete', 'Version 2.1.0 deployed to production', {
131
+ * via: 'slack',
132
+ * metadata: { version: '2.1.0', environment: 'production' },
133
+ * })
134
+ * ```
135
+ */
136
+ notify.rich = async (
137
+ target: ActionTarget,
138
+ title: string,
139
+ body: string,
140
+ options: NotifyOptions = {}
141
+ ): Promise<NotifyResult> => {
142
+ const message = `**${title}**\n\n${body}`
143
+ return notify(target, message, options)
144
+ }
145
+
146
+ /**
147
+ * Send notifications in batch
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * await notify.batch([
152
+ * { target: alice, message: 'Task 1 complete' },
153
+ * { target: bob, message: 'Task 2 complete' },
154
+ * { target: team, message: 'All tasks done', options: { via: 'slack' } },
155
+ * ])
156
+ * ```
157
+ */
158
+ notify.batch = async (
159
+ notifications: Array<{
160
+ target: ActionTarget
161
+ message: string
162
+ options?: NotifyOptions
163
+ }>
164
+ ): Promise<NotifyResult[]> => {
165
+ return Promise.all(
166
+ notifications.map(({ target, message, options }) => notify(target, message, options))
167
+ )
168
+ }
169
+
170
+ /**
171
+ * Schedule a notification for later
172
+ *
173
+ * @example
174
+ * ```ts
175
+ * // Schedule for specific time
176
+ * await notify.schedule(alice, 'Meeting in 15 minutes', new Date('2024-01-15T14:45:00Z'))
177
+ *
178
+ * // Schedule with delay
179
+ * await notify.schedule(alice, 'Reminder', 60000) // 1 minute
180
+ * ```
181
+ */
182
+ notify.schedule = async (
183
+ target: ActionTarget,
184
+ message: string,
185
+ when: Date | number,
186
+ options: NotifyOptions = {}
187
+ ): Promise<{ scheduled: true; scheduledFor: Date; messageId: string }> => {
188
+ const scheduledFor = when instanceof Date ? when : new Date(Date.now() + when)
189
+
190
+ // In a real implementation, this would store the scheduled notification
191
+ return {
192
+ scheduled: true,
193
+ scheduledFor,
194
+ messageId: generateMessageId('scheduled'),
195
+ }
196
+ }
197
+
198
+ // ============================================================================
199
+ // Internal Helpers
200
+ // ============================================================================
201
+
202
+ /**
203
+ * Resolve an action target to contacts and recipients
204
+ */
205
+ function resolveTarget(target: ActionTarget): {
206
+ contacts: Contacts
207
+ recipients: WorkerRef[]
208
+ } {
209
+ if (typeof target === 'string') {
210
+ // Just an ID - return empty contacts, would need to look up
211
+ return {
212
+ contacts: {},
213
+ recipients: [{ id: target }],
214
+ }
215
+ }
216
+
217
+ if ('contacts' in target) {
218
+ // Worker or Team
219
+ const recipients: WorkerRef[] =
220
+ 'members' in target
221
+ ? target.members // Team
222
+ : [{ id: target.id, type: target.type, name: target.name }] // Worker
223
+
224
+ return {
225
+ contacts: target.contacts,
226
+ recipients,
227
+ }
228
+ }
229
+
230
+ // WorkerRef - no contacts available
231
+ return {
232
+ contacts: {},
233
+ recipients: [target],
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Determine which channels to use based on options and contacts
239
+ */
240
+ function resolveChannels(
241
+ via: ContactChannel | ContactChannel[] | undefined,
242
+ contacts: Contacts,
243
+ priority: string
244
+ ): ContactChannel[] {
245
+ // If specific channels requested, use those
246
+ if (via) {
247
+ const requested = Array.isArray(via) ? via : [via]
248
+ // Filter to only channels that exist in contacts
249
+ return requested.filter((channel) => contacts[channel] !== undefined)
250
+ }
251
+
252
+ // Otherwise, use available channels based on priority
253
+ const available = Object.keys(contacts) as ContactChannel[]
254
+
255
+ if (available.length === 0) {
256
+ return []
257
+ }
258
+
259
+ const firstChannel = available[0]
260
+ if (!firstChannel) {
261
+ return []
262
+ }
263
+
264
+ // For urgent, try multiple channels
265
+ if (priority === 'urgent') {
266
+ const urgentChannels: ContactChannel[] = ['slack', 'sms', 'phone']
267
+ return available.filter((c) => urgentChannels.includes(c))
268
+ }
269
+
270
+ // Default to first available
271
+ return [firstChannel]
272
+ }
273
+
274
+ /**
275
+ * Send a notification to a specific channel
276
+ */
277
+ async function sendToChannel(
278
+ channel: ContactChannel,
279
+ message: string,
280
+ contacts: Contacts,
281
+ options: { priority?: string; metadata?: Record<string, unknown> }
282
+ ): Promise<void> {
283
+ const contact = contacts[channel]
284
+
285
+ if (!contact) {
286
+ throw new Error(`No ${channel} contact configured`)
287
+ }
288
+
289
+ // In a real implementation, this would:
290
+ // 1. Format the message for the channel
291
+ // 2. Send via the appropriate API (Slack, SendGrid, Twilio, etc.)
292
+ // 3. Handle delivery confirmation
293
+
294
+ // For now, simulate success
295
+ await new Promise((resolve) => setTimeout(resolve, 10))
296
+ }
297
+
298
+ /**
299
+ * Generate a unique message ID
300
+ */
301
+ function generateMessageId(prefix = 'msg'): string {
302
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
303
+ }
package/src/role.ts ADDED
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Role definition for digital workers
3
+ */
4
+
5
+ import type { WorkerRole } from './types.js'
6
+
7
+ /**
8
+ * Define a worker role
9
+ *
10
+ * Roles define responsibilities, skills, and permissions for workers
11
+ * (both AI agents and humans) within an organization.
12
+ *
13
+ * @param definition - Role definition
14
+ * @returns The defined role
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * const engineer = Role({
19
+ * name: 'Software Engineer',
20
+ * description: 'Builds and maintains software systems',
21
+ * responsibilities: [
22
+ * 'Write clean, maintainable code',
23
+ * 'Review pull requests',
24
+ * 'Fix bugs and issues',
25
+ * 'Participate in architecture decisions',
26
+ * ],
27
+ * skills: ['TypeScript', 'React', 'Node.js'],
28
+ * type: 'hybrid', // Can be filled by AI or human
29
+ * })
30
+ * ```
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const supportAgent = Role({
35
+ * name: 'Customer Support Agent',
36
+ * description: 'Assists customers with inquiries and issues',
37
+ * responsibilities: [
38
+ * 'Respond to customer inquiries',
39
+ * 'Troubleshoot issues',
40
+ * 'Escalate complex problems',
41
+ * 'Maintain customer satisfaction',
42
+ * ],
43
+ * type: 'ai', // AI-first role
44
+ * })
45
+ * ```
46
+ */
47
+ export function Role(definition: Omit<WorkerRole, 'type'> & { type?: WorkerRole['type'] }): WorkerRole {
48
+ return {
49
+ type: 'hybrid', // Default to hybrid (can be AI or human)
50
+ ...definition,
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Create an AI-specific role
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * const dataAnalyst = Role.ai({
60
+ * name: 'Data Analyst',
61
+ * description: 'Analyzes data and generates insights',
62
+ * responsibilities: [
63
+ * 'Process large datasets',
64
+ * 'Generate reports',
65
+ * 'Identify trends and patterns',
66
+ * ],
67
+ * })
68
+ * ```
69
+ */
70
+ Role.ai = (definition: Omit<WorkerRole, 'type'>): WorkerRole => ({
71
+ ...definition,
72
+ type: 'ai',
73
+ })
74
+
75
+ /**
76
+ * Create a human-specific role
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * const manager = Role.human({
81
+ * name: 'Engineering Manager',
82
+ * description: 'Leads engineering team and makes strategic decisions',
83
+ * responsibilities: [
84
+ * 'Team leadership and mentoring',
85
+ * 'Strategic planning',
86
+ * 'Performance reviews',
87
+ * 'Budget management',
88
+ * ],
89
+ * })
90
+ * ```
91
+ */
92
+ Role.human = (definition: Omit<WorkerRole, 'type'>): WorkerRole => ({
93
+ ...definition,
94
+ type: 'human',
95
+ })
96
+
97
+ /**
98
+ * Create a hybrid role (can be AI or human)
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * const contentWriter = Role.hybrid({
103
+ * name: 'Content Writer',
104
+ * description: 'Creates written content for various channels',
105
+ * responsibilities: [
106
+ * 'Write blog posts and articles',
107
+ * 'Create marketing copy',
108
+ * 'Edit and proofread content',
109
+ * ],
110
+ * })
111
+ * ```
112
+ */
113
+ Role.hybrid = (definition: Omit<WorkerRole, 'type'>): WorkerRole => ({
114
+ ...definition,
115
+ type: 'hybrid',
116
+ })
package/src/team.ts ADDED
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Team definition for digital workers
3
+ */
4
+
5
+ import type { WorkerTeam } from './types.js'
6
+
7
+ /**
8
+ * Define a team of workers
9
+ *
10
+ * Teams organize workers (AI agents and humans) into cohesive units
11
+ * with shared goals and responsibilities.
12
+ *
13
+ * @param definition - Team definition
14
+ * @returns The defined team
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * const engineeringTeam = Team({
19
+ * name: 'Engineering',
20
+ * description: 'Product engineering team',
21
+ * members: [
22
+ * { id: 'alice', name: 'Alice', role: 'Senior Engineer', type: 'human' },
23
+ * { id: 'bob', name: 'Bob', role: 'Engineer', type: 'human' },
24
+ * { id: 'ai-reviewer', name: 'Code Reviewer', role: 'Code Reviewer', type: 'ai' },
25
+ * { id: 'ai-tester', name: 'Test Generator', role: 'Test Engineer', type: 'ai' },
26
+ * ],
27
+ * goals: [
28
+ * 'Ship features on schedule',
29
+ * 'Maintain code quality',
30
+ * 'Reduce technical debt',
31
+ * ],
32
+ * lead: 'alice',
33
+ * })
34
+ * ```
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const supportTeam = Team({
39
+ * name: 'Customer Support',
40
+ * description: '24/7 customer support team',
41
+ * members: [
42
+ * { id: 'support-ai-1', name: 'Support Bot Alpha', role: 'Support Agent', type: 'ai' },
43
+ * { id: 'support-ai-2', name: 'Support Bot Beta', role: 'Support Agent', type: 'ai' },
44
+ * { id: 'escalation-human', name: 'Jane', role: 'Senior Support', type: 'human' },
45
+ * ],
46
+ * goals: [
47
+ * 'Maintain 95% satisfaction rate',
48
+ * 'Response time under 5 minutes',
49
+ * 'First contact resolution > 80%',
50
+ * ],
51
+ * lead: 'escalation-human',
52
+ * })
53
+ * ```
54
+ */
55
+ export function Team(definition: WorkerTeam): WorkerTeam {
56
+ return definition
57
+ }
58
+
59
+ /**
60
+ * Add a member to a team
61
+ *
62
+ * @param team - The team to add to
63
+ * @param member - The member to add
64
+ * @returns Updated team
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const updatedTeam = Team.addMember(engineeringTeam, {
69
+ * id: 'charlie',
70
+ * name: 'Charlie',
71
+ * role: 'Junior Engineer',
72
+ * type: 'human',
73
+ * })
74
+ * ```
75
+ */
76
+ Team.addMember = (
77
+ team: WorkerTeam,
78
+ member: WorkerTeam['members'][number]
79
+ ): WorkerTeam => ({
80
+ ...team,
81
+ members: [...team.members, member],
82
+ })
83
+
84
+ /**
85
+ * Remove a member from a team
86
+ *
87
+ * @param team - The team to remove from
88
+ * @param memberId - ID of the member to remove
89
+ * @returns Updated team
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * const updatedTeam = Team.removeMember(engineeringTeam, 'bob')
94
+ * ```
95
+ */
96
+ Team.removeMember = (team: WorkerTeam, memberId: string): WorkerTeam => ({
97
+ ...team,
98
+ members: team.members.filter((m) => m.id !== memberId),
99
+ })
100
+
101
+ /**
102
+ * Get all AI members of a team
103
+ *
104
+ * @param team - The team
105
+ * @returns Array of AI members
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * const aiMembers = Team.aiMembers(supportTeam)
110
+ * console.log(aiMembers) // [Support Bot Alpha, Support Bot Beta]
111
+ * ```
112
+ */
113
+ Team.aiMembers = (team: WorkerTeam) => team.members.filter((m) => m.type === 'agent')
114
+
115
+ /**
116
+ * Get all human members of a team
117
+ *
118
+ * @param team - The team
119
+ * @returns Array of human members
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * const humans = Team.humanMembers(engineeringTeam)
124
+ * console.log(humans) // [Alice, Bob]
125
+ * ```
126
+ */
127
+ Team.humanMembers = (team: WorkerTeam) => team.members.filter((m) => m.type === 'human')
128
+
129
+ /**
130
+ * Get members by role
131
+ *
132
+ * @param team - The team
133
+ * @param role - Role to filter by
134
+ * @returns Array of members with that role
135
+ *
136
+ * @example
137
+ * ```ts
138
+ * const engineers = Team.byRole(engineeringTeam, 'Engineer')
139
+ * ```
140
+ */
141
+ Team.byRole = (team: WorkerTeam, role: string) =>
142
+ team.members.filter((m) => m.role === role)