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/approve.ts ADDED
@@ -0,0 +1,317 @@
1
+ /**
2
+ * Approval request functionality for digital workers
3
+ */
4
+
5
+ import type {
6
+ Worker,
7
+ Team,
8
+ WorkerRef,
9
+ ActionTarget,
10
+ ContactChannel,
11
+ ApprovalResult,
12
+ ApprovalOptions,
13
+ Contacts,
14
+ } from './types.js'
15
+
16
+ /**
17
+ * Request approval from a worker or team
18
+ *
19
+ * Routes approval requests through the specified channel and waits for a response.
20
+ *
21
+ * @param request - What is being requested for approval
22
+ * @param target - The worker or team to request approval from
23
+ * @param options - Approval options
24
+ * @returns Promise resolving to approval result
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * // Request approval from a worker
29
+ * const result = await approve('Expense: $500 for AWS', manager, {
30
+ * via: 'slack',
31
+ * context: { amount: 500, category: 'Infrastructure' },
32
+ * })
33
+ *
34
+ * if (result.approved) {
35
+ * console.log(`Approved by ${result.approvedBy?.name}`)
36
+ * }
37
+ *
38
+ * // Request approval from a team
39
+ * const result = await approve('Deploy v2.1.0 to production', opsTeam, {
40
+ * via: 'slack',
41
+ * })
42
+ * ```
43
+ */
44
+ export async function approve(
45
+ request: string,
46
+ target: ActionTarget,
47
+ options: ApprovalOptions = {}
48
+ ): Promise<ApprovalResult> {
49
+ const { via, timeout, context, escalate = false } = options
50
+
51
+ // Resolve target to get contacts and approver info
52
+ const { contacts, approver } = resolveTarget(target)
53
+
54
+ // Determine which channel to use
55
+ const channel = resolveChannel(via, contacts)
56
+
57
+ if (!channel) {
58
+ throw new Error('No valid channel available for approval request')
59
+ }
60
+
61
+ // Send the approval request and wait for response
62
+ const response = await sendApprovalRequest(channel, request, contacts, {
63
+ timeout,
64
+ context,
65
+ approver,
66
+ escalate,
67
+ })
68
+
69
+ return {
70
+ approved: response.approved,
71
+ approvedBy: approver,
72
+ approvedAt: new Date(),
73
+ notes: response.notes,
74
+ via: channel,
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Request approval with structured decision context
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * const result = await approve.withContext(
84
+ * 'Migrate to new database',
85
+ * cto,
86
+ * {
87
+ * pros: ['Better performance', 'Lower cost'],
88
+ * cons: ['Migration effort', 'Downtime required'],
89
+ * risks: ['Data loss', 'Service disruption'],
90
+ * mitigations: ['Backup strategy', 'Staged rollout'],
91
+ * },
92
+ * { via: 'email' }
93
+ * )
94
+ * ```
95
+ */
96
+ approve.withContext = async (
97
+ request: string,
98
+ target: ActionTarget,
99
+ decision: {
100
+ pros?: string[]
101
+ cons?: string[]
102
+ risks?: string[]
103
+ mitigations?: string[]
104
+ alternatives?: string[]
105
+ },
106
+ options: ApprovalOptions = {}
107
+ ): Promise<ApprovalResult> => {
108
+ return approve(request, target, {
109
+ ...options,
110
+ context: {
111
+ ...options.context,
112
+ decision,
113
+ },
114
+ })
115
+ }
116
+
117
+ /**
118
+ * Request batch approval for multiple items
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * const results = await approve.batch([
123
+ * 'Expense: $500 for AWS',
124
+ * 'Expense: $200 for office supplies',
125
+ * 'Expense: $1000 for conference ticket',
126
+ * ], finance, { via: 'email' })
127
+ *
128
+ * const approved = results.filter(r => r.approved)
129
+ * ```
130
+ */
131
+ approve.batch = async (
132
+ requests: string[],
133
+ target: ActionTarget,
134
+ options: ApprovalOptions = {}
135
+ ): Promise<ApprovalResult[]> => {
136
+ return Promise.all(requests.map((request) => approve(request, target, options)))
137
+ }
138
+
139
+ /**
140
+ * Request approval with a deadline
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * const result = await approve.withDeadline(
145
+ * 'Release v2.0',
146
+ * manager,
147
+ * new Date('2024-01-15T17:00:00Z'),
148
+ * { via: 'slack' }
149
+ * )
150
+ * ```
151
+ */
152
+ approve.withDeadline = async (
153
+ request: string,
154
+ target: ActionTarget,
155
+ deadline: Date,
156
+ options: ApprovalOptions = {}
157
+ ): Promise<ApprovalResult> => {
158
+ const timeout = deadline.getTime() - Date.now()
159
+ return approve(request, target, {
160
+ ...options,
161
+ timeout: Math.max(0, timeout),
162
+ context: {
163
+ ...options.context,
164
+ deadline: deadline.toISOString(),
165
+ },
166
+ })
167
+ }
168
+
169
+ /**
170
+ * Request approval from multiple approvers (any one can approve)
171
+ *
172
+ * @example
173
+ * ```ts
174
+ * const result = await approve.any(
175
+ * 'Urgent: Production fix',
176
+ * [alice, bob, charlie],
177
+ * { via: 'slack' }
178
+ * )
179
+ * ```
180
+ */
181
+ approve.any = async (
182
+ request: string,
183
+ targets: ActionTarget[],
184
+ options: ApprovalOptions = {}
185
+ ): Promise<ApprovalResult> => {
186
+ // Race all approval requests - first to respond wins
187
+ return Promise.race(targets.map((target) => approve(request, target, options)))
188
+ }
189
+
190
+ /**
191
+ * Request approval from multiple approvers (all must approve)
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * const result = await approve.all(
196
+ * 'Major infrastructure change',
197
+ * [cto, vpe, securityLead],
198
+ * { via: 'email' }
199
+ * )
200
+ * ```
201
+ */
202
+ approve.all = async (
203
+ request: string,
204
+ targets: ActionTarget[],
205
+ options: ApprovalOptions = {}
206
+ ): Promise<ApprovalResult & { approvals: ApprovalResult[] }> => {
207
+ const results = await Promise.all(targets.map((target) => approve(request, target, options)))
208
+
209
+ const allApproved = results.every((r) => r.approved)
210
+
211
+ return {
212
+ approved: allApproved,
213
+ approvedAt: new Date(),
214
+ notes: allApproved
215
+ ? 'All approvers approved'
216
+ : `${results.filter((r) => !r.approved).length} rejection(s)`,
217
+ approvals: results,
218
+ }
219
+ }
220
+
221
+ // ============================================================================
222
+ // Internal Helpers
223
+ // ============================================================================
224
+
225
+ /**
226
+ * Resolve an action target to contacts and approver
227
+ */
228
+ function resolveTarget(target: ActionTarget): {
229
+ contacts: Contacts
230
+ approver: WorkerRef
231
+ } {
232
+ if (typeof target === 'string') {
233
+ return {
234
+ contacts: {},
235
+ approver: { id: target },
236
+ }
237
+ }
238
+
239
+ if ('contacts' in target) {
240
+ // Worker or Team
241
+ let approver: WorkerRef
242
+ if ('members' in target) {
243
+ // Team - use lead or first member
244
+ approver = target.lead ?? target.members[0] ?? { id: target.id }
245
+ } else {
246
+ // Worker
247
+ approver = { id: target.id, type: target.type, name: target.name }
248
+ }
249
+
250
+ return {
251
+ contacts: target.contacts,
252
+ approver,
253
+ }
254
+ }
255
+
256
+ // WorkerRef
257
+ return {
258
+ contacts: {},
259
+ approver: target,
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Determine which channel to use
265
+ */
266
+ function resolveChannel(
267
+ via: ContactChannel | ContactChannel[] | undefined,
268
+ contacts: Contacts
269
+ ): ContactChannel | null {
270
+ if (via) {
271
+ const requested = Array.isArray(via) ? via[0] : via
272
+ if (requested && contacts[requested] !== undefined) {
273
+ return requested
274
+ }
275
+ }
276
+
277
+ // Default to first available
278
+ const available = Object.keys(contacts) as ContactChannel[]
279
+ const first = available[0]
280
+ return first ?? null
281
+ }
282
+
283
+ /**
284
+ * Send an approval request to a channel and wait for response
285
+ */
286
+ async function sendApprovalRequest(
287
+ channel: ContactChannel,
288
+ request: string,
289
+ contacts: Contacts,
290
+ options: {
291
+ timeout?: number
292
+ context?: Record<string, unknown>
293
+ approver: WorkerRef
294
+ escalate?: boolean
295
+ }
296
+ ): Promise<{ approved: boolean; notes?: string }> {
297
+ const contact = contacts[channel]
298
+
299
+ if (!contact) {
300
+ throw new Error(`No ${channel} contact configured`)
301
+ }
302
+
303
+ // In a real implementation, this would:
304
+ // 1. Format the request for the channel (Slack blocks, email HTML, etc.)
305
+ // 2. Send via the appropriate API
306
+ // 3. Wait for response (polling, webhook, interactive message, etc.)
307
+ // 4. Handle timeout and escalation
308
+
309
+ // For now, simulate a pending response
310
+ await new Promise((resolve) => setTimeout(resolve, 10))
311
+
312
+ // Return a placeholder - real impl would wait for actual response
313
+ return {
314
+ approved: false,
315
+ notes: 'Approval pending - waiting for response',
316
+ }
317
+ }
package/src/ask.ts ADDED
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Question/answer functionality for digital workers
3
+ */
4
+
5
+ import { generateObject } from 'ai-functions'
6
+ import type { SimpleSchema } from 'ai-functions'
7
+ import type {
8
+ Worker,
9
+ Team,
10
+ WorkerRef,
11
+ ActionTarget,
12
+ ContactChannel,
13
+ AskResult,
14
+ AskOptions,
15
+ Contacts,
16
+ } from './types.js'
17
+
18
+ /**
19
+ * Ask a question to a worker or team
20
+ *
21
+ * Routes questions through the specified channel and waits for a response.
22
+ *
23
+ * @param target - The worker or team to ask
24
+ * @param question - The question to ask
25
+ * @param options - Ask options
26
+ * @returns Promise resolving to the answer
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * // Ask a simple question
31
+ * const result = await ask(alice, 'What is the company holiday policy?', {
32
+ * via: 'slack',
33
+ * })
34
+ * console.log(result.answer)
35
+ *
36
+ * // Ask with structured response
37
+ * const result = await ask(ceo, 'What are our Q1 priorities?', {
38
+ * via: 'email',
39
+ * schema: {
40
+ * priorities: ['List of priorities'],
41
+ * reasoning: 'Why these priorities were chosen',
42
+ * },
43
+ * })
44
+ * ```
45
+ */
46
+ export async function ask<T = string>(
47
+ target: ActionTarget,
48
+ question: string,
49
+ options: AskOptions = {}
50
+ ): Promise<AskResult<T>> {
51
+ const { via, schema, timeout, context } = options
52
+
53
+ // Resolve target to get contacts and recipient info
54
+ const { contacts, recipient } = resolveTarget(target)
55
+
56
+ // Determine which channel to use
57
+ const channel = resolveChannel(via, contacts)
58
+
59
+ if (!channel) {
60
+ throw new Error('No valid channel available to ask question')
61
+ }
62
+
63
+ // Send the question and wait for response
64
+ const response = await sendQuestion<T>(channel, question, contacts, {
65
+ schema,
66
+ timeout,
67
+ context,
68
+ recipient,
69
+ })
70
+
71
+ return {
72
+ answer: response.answer,
73
+ answeredBy: recipient,
74
+ answeredAt: new Date(),
75
+ via: channel,
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Ask an AI agent directly (no human routing)
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * const answer = await ask.ai('What is our refund policy?', {
85
+ * policies: [...],
86
+ * customerContext: {...},
87
+ * })
88
+ * ```
89
+ */
90
+ ask.ai = async <T = string>(
91
+ question: string,
92
+ context?: Record<string, unknown>,
93
+ schema?: SimpleSchema
94
+ ): Promise<T> => {
95
+ if (schema) {
96
+ const result = await generateObject({
97
+ model: 'sonnet',
98
+ schema,
99
+ prompt: question,
100
+ system: context
101
+ ? `Use the following context to answer the question:\n\n${JSON.stringify(context, null, 2)}`
102
+ : undefined,
103
+ })
104
+ return result.object as T
105
+ }
106
+
107
+ const result = await generateObject({
108
+ model: 'sonnet',
109
+ schema: { answer: 'The answer to the question' },
110
+ prompt: question,
111
+ system: context
112
+ ? `Use the following context to answer the question:\n\n${JSON.stringify(context, null, 2)}`
113
+ : undefined,
114
+ })
115
+
116
+ return (result.object as { answer: T }).answer
117
+ }
118
+
119
+ /**
120
+ * Ask multiple questions at once
121
+ *
122
+ * @example
123
+ * ```ts
124
+ * const results = await ask.batch(hr, [
125
+ * 'What is the vacation policy?',
126
+ * 'What is the remote work policy?',
127
+ * 'What is the expense policy?',
128
+ * ], { via: 'email' })
129
+ * ```
130
+ */
131
+ ask.batch = async <T = string>(
132
+ target: ActionTarget,
133
+ questions: string[],
134
+ options: AskOptions = {}
135
+ ): Promise<Array<AskResult<T>>> => {
136
+ return Promise.all(questions.map((q) => ask<T>(target, q, options)))
137
+ }
138
+
139
+ /**
140
+ * Ask for clarification on something
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * const clarification = await ask.clarify(devops, 'The deployment process')
145
+ * ```
146
+ */
147
+ ask.clarify = async (
148
+ target: ActionTarget,
149
+ topic: string,
150
+ options: AskOptions = {}
151
+ ): Promise<AskResult<string>> => {
152
+ return ask<string>(target, `Can you clarify: ${topic}`, options)
153
+ }
154
+
155
+ /**
156
+ * Ask a yes/no question
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * const result = await ask.yesNo(manager, 'Should we proceed with the release?', {
161
+ * via: 'slack',
162
+ * })
163
+ * if (result.answer === 'yes') {
164
+ * // proceed
165
+ * }
166
+ * ```
167
+ */
168
+ ask.yesNo = async (
169
+ target: ActionTarget,
170
+ question: string,
171
+ options: AskOptions = {}
172
+ ): Promise<AskResult<'yes' | 'no'>> => {
173
+ return ask<'yes' | 'no'>(target, question, {
174
+ ...options,
175
+ schema: {
176
+ answer: 'Answer: yes or no',
177
+ },
178
+ })
179
+ }
180
+
181
+ /**
182
+ * Ask for a choice from options
183
+ *
184
+ * @example
185
+ * ```ts
186
+ * const result = await ask.choose(designer, 'Which color scheme?', {
187
+ * choices: ['Light', 'Dark', 'System'],
188
+ * via: 'slack',
189
+ * })
190
+ * ```
191
+ */
192
+ ask.choose = async <T extends string>(
193
+ target: ActionTarget,
194
+ question: string,
195
+ choices: T[],
196
+ options: AskOptions = {}
197
+ ): Promise<AskResult<T>> => {
198
+ const choiceList = choices.map((c, i) => `${i + 1}. ${c}`).join('\n')
199
+ const fullQuestion = `${question}\n\nOptions:\n${choiceList}`
200
+
201
+ return ask<T>(target, fullQuestion, {
202
+ ...options,
203
+ schema: {
204
+ answer: `One of: ${choices.join(', ')}`,
205
+ },
206
+ })
207
+ }
208
+
209
+ // ============================================================================
210
+ // Internal Helpers
211
+ // ============================================================================
212
+
213
+ /**
214
+ * Resolve an action target to contacts and recipient
215
+ */
216
+ function resolveTarget(target: ActionTarget): {
217
+ contacts: Contacts
218
+ recipient: WorkerRef
219
+ } {
220
+ if (typeof target === 'string') {
221
+ return {
222
+ contacts: {},
223
+ recipient: { id: target },
224
+ }
225
+ }
226
+
227
+ if ('contacts' in target) {
228
+ // Worker or Team
229
+ let recipient: WorkerRef
230
+ if ('members' in target) {
231
+ // Team - ask lead or first member
232
+ recipient = target.lead ?? target.members[0] ?? { id: target.id }
233
+ } else {
234
+ // Worker
235
+ recipient = { id: target.id, type: target.type, name: target.name }
236
+ }
237
+
238
+ return {
239
+ contacts: target.contacts,
240
+ recipient,
241
+ }
242
+ }
243
+
244
+ // WorkerRef
245
+ return {
246
+ contacts: {},
247
+ recipient: target,
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Determine which channel to use
253
+ */
254
+ function resolveChannel(
255
+ via: ContactChannel | ContactChannel[] | undefined,
256
+ contacts: Contacts
257
+ ): ContactChannel | null {
258
+ if (via) {
259
+ const requested = Array.isArray(via) ? via[0] : via
260
+ if (requested && contacts[requested] !== undefined) {
261
+ return requested
262
+ }
263
+ }
264
+
265
+ // Default to first available
266
+ const available = Object.keys(contacts) as ContactChannel[]
267
+ const first = available[0]
268
+ return first ?? null
269
+ }
270
+
271
+ /**
272
+ * Send a question to a channel and wait for response
273
+ */
274
+ async function sendQuestion<T>(
275
+ channel: ContactChannel,
276
+ question: string,
277
+ contacts: Contacts,
278
+ options: {
279
+ schema?: SimpleSchema
280
+ timeout?: number
281
+ context?: Record<string, unknown>
282
+ recipient: WorkerRef
283
+ }
284
+ ): Promise<{ answer: T }> {
285
+ const contact = contacts[channel]
286
+
287
+ if (!contact) {
288
+ throw new Error(`No ${channel} contact configured`)
289
+ }
290
+
291
+ // In a real implementation, this would:
292
+ // 1. Format the question for the channel
293
+ // 2. Send via the appropriate API
294
+ // 3. Wait for response (polling, webhook, etc.)
295
+ // 4. Parse and validate the response
296
+
297
+ // For now, simulate a pending response
298
+ await new Promise((resolve) => setTimeout(resolve, 10))
299
+
300
+ // Return a placeholder - real impl would wait for actual response
301
+ return {
302
+ answer: 'Waiting for response...' as T,
303
+ }
304
+ }