digital-workers 2.1.3 → 2.4.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 (183) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +17 -0
  3. package/README.md +2 -0
  4. package/dist/actions.d.ts.map +1 -1
  5. package/dist/actions.js +33 -21
  6. package/dist/actions.js.map +1 -1
  7. package/dist/agent-comms.d.ts.map +1 -1
  8. package/dist/agent-comms.js +36 -25
  9. package/dist/agent-comms.js.map +1 -1
  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.js +3 -3
  23. package/dist/capability-tiers.js.map +1 -1
  24. package/dist/cascade-context.d.ts +28 -28
  25. package/dist/client.d.ts +162 -0
  26. package/dist/client.d.ts.map +1 -0
  27. package/dist/client.js +64 -0
  28. package/dist/client.js.map +1 -0
  29. package/dist/decide.d.ts +42 -6
  30. package/dist/decide.d.ts.map +1 -1
  31. package/dist/decide.js +54 -11
  32. package/dist/decide.js.map +1 -1
  33. package/dist/do.d.ts +36 -7
  34. package/dist/do.d.ts.map +1 -1
  35. package/dist/do.js +82 -39
  36. package/dist/do.js.map +1 -1
  37. package/dist/error-escalation.d.ts.map +1 -1
  38. package/dist/error-escalation.js +38 -38
  39. package/dist/error-escalation.js.map +1 -1
  40. package/dist/generate.d.ts +48 -7
  41. package/dist/generate.d.ts.map +1 -1
  42. package/dist/generate.js +49 -8
  43. package/dist/generate.js.map +1 -1
  44. package/dist/goals.d.ts +10 -9
  45. package/dist/goals.d.ts.map +1 -1
  46. package/dist/goals.js +30 -24
  47. package/dist/goals.js.map +1 -1
  48. package/dist/image.d.ts +189 -0
  49. package/dist/image.d.ts.map +1 -0
  50. package/dist/image.js +528 -0
  51. package/dist/image.js.map +1 -0
  52. package/dist/index.d.ts +49 -2
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +58 -2
  55. package/dist/index.js.map +1 -1
  56. package/dist/is.d.ts +45 -10
  57. package/dist/is.d.ts.map +1 -1
  58. package/dist/is.js +56 -21
  59. package/dist/is.js.map +1 -1
  60. package/dist/kpis.d.ts +24 -15
  61. package/dist/kpis.d.ts.map +1 -1
  62. package/dist/kpis.js +16 -14
  63. package/dist/kpis.js.map +1 -1
  64. package/dist/load-balancing.d.ts.map +1 -1
  65. package/dist/load-balancing.js +124 -38
  66. package/dist/load-balancing.js.map +1 -1
  67. package/dist/logger.d.ts +76 -0
  68. package/dist/logger.d.ts.map +1 -0
  69. package/dist/logger.js +39 -0
  70. package/dist/logger.js.map +1 -0
  71. package/dist/notify.d.ts +38 -9
  72. package/dist/notify.d.ts.map +1 -1
  73. package/dist/notify.js +72 -17
  74. package/dist/notify.js.map +1 -1
  75. package/dist/role.d.ts +5 -4
  76. package/dist/role.d.ts.map +1 -1
  77. package/dist/role.js +13 -10
  78. package/dist/role.js.map +1 -1
  79. package/dist/runtime.d.ts +310 -0
  80. package/dist/runtime.d.ts.map +1 -0
  81. package/dist/runtime.js +510 -0
  82. package/dist/runtime.js.map +1 -0
  83. package/dist/team.d.ts +11 -6
  84. package/dist/team.d.ts.map +1 -1
  85. package/dist/team.js +22 -15
  86. package/dist/team.js.map +1 -1
  87. package/dist/transports/email.d.ts +318 -0
  88. package/dist/transports/email.d.ts.map +1 -0
  89. package/dist/transports/email.js +779 -0
  90. package/dist/transports/email.js.map +1 -0
  91. package/dist/transports/slack.d.ts +515 -0
  92. package/dist/transports/slack.d.ts.map +1 -0
  93. package/dist/transports/slack.js +844 -0
  94. package/dist/transports/slack.js.map +1 -0
  95. package/dist/transports.d.ts.map +1 -1
  96. package/dist/transports.js +44 -25
  97. package/dist/transports.js.map +1 -1
  98. package/dist/types.d.ts +141 -19
  99. package/dist/types.d.ts.map +1 -1
  100. package/dist/types.js +5 -0
  101. package/dist/types.js.map +1 -1
  102. package/dist/utils/id.d.ts +19 -0
  103. package/dist/utils/id.d.ts.map +1 -0
  104. package/dist/utils/id.js +21 -0
  105. package/dist/utils/id.js.map +1 -0
  106. package/dist/video.d.ts +203 -0
  107. package/dist/video.d.ts.map +1 -0
  108. package/dist/video.js +528 -0
  109. package/dist/video.js.map +1 -0
  110. package/dist/worker.d.ts +343 -0
  111. package/dist/worker.d.ts.map +1 -0
  112. package/dist/worker.js +698 -0
  113. package/dist/worker.js.map +1 -0
  114. package/package.json +32 -14
  115. package/src/actions.ts +39 -30
  116. package/src/agent-comms.ts +54 -92
  117. package/src/approve.ts +91 -20
  118. package/src/ask.ts +99 -25
  119. package/src/browse.ts +627 -0
  120. package/src/capability-tiers.ts +5 -5
  121. package/src/client.ts +221 -0
  122. package/src/decide.ts +81 -35
  123. package/src/do.ts +98 -52
  124. package/src/error-escalation.ts +55 -67
  125. package/src/generate.ts +52 -18
  126. package/src/goals.ts +36 -27
  127. package/src/image.ts +816 -0
  128. package/src/index.ts +187 -2
  129. package/src/is.ts +59 -25
  130. package/src/kpis.ts +41 -36
  131. package/src/load-balancing.ts +132 -46
  132. package/src/logger.ts +93 -0
  133. package/src/notify.ts +78 -17
  134. package/src/role.ts +30 -20
  135. package/src/runtime.ts +796 -0
  136. package/src/team.ts +24 -19
  137. package/src/transports/email.ts +1160 -0
  138. package/src/transports/slack.ts +1320 -0
  139. package/src/transports.ts +58 -43
  140. package/src/types.ts +174 -46
  141. package/src/utils/id.ts +21 -0
  142. package/src/video.ts +906 -0
  143. package/src/worker.ts +1007 -0
  144. package/test/approve.test.ts +305 -0
  145. package/test/ask.test.ts +274 -0
  146. package/test/browse.test.ts +361 -0
  147. package/test/decide.test.ts +252 -0
  148. package/test/do.test.ts +144 -0
  149. package/test/error-logging.test.ts +357 -0
  150. package/test/generate.test.ts +319 -0
  151. package/test/image.test.ts +398 -0
  152. package/test/is.test.ts +287 -0
  153. package/test/load-balancing-safety.test.ts +404 -0
  154. package/test/notify.test.ts +434 -0
  155. package/test/primitives.test.ts +320 -0
  156. package/test/runtime-integration.test.ts +892 -0
  157. package/test/transports/crypto.test.ts +230 -0
  158. package/test/transports/email.test.ts +866 -0
  159. package/test/transports/id-generation.test.ts +91 -0
  160. package/test/transports/slack.test.ts +760 -0
  161. package/test/type-safety.test.ts +834 -0
  162. package/test/types.test.ts +60 -2
  163. package/test/video.test.ts +530 -0
  164. package/test/worker.test.ts +1433 -0
  165. package/tsconfig.json +4 -1
  166. package/vitest.config.ts +42 -0
  167. package/wrangler.jsonc +36 -0
  168. package/LICENSE +0 -21
  169. package/src/actions.js +0 -436
  170. package/src/approve.js +0 -234
  171. package/src/ask.js +0 -226
  172. package/src/decide.js +0 -244
  173. package/src/do.js +0 -227
  174. package/src/generate.js +0 -298
  175. package/src/goals.js +0 -205
  176. package/src/index.js +0 -68
  177. package/src/is.js +0 -317
  178. package/src/kpis.js +0 -270
  179. package/src/notify.js +0 -219
  180. package/src/role.js +0 -110
  181. package/src/team.js +0 -130
  182. package/src/transports.js +0 -357
  183. package/src/types.js +0 -71
package/src/index.ts CHANGED
@@ -6,7 +6,29 @@
6
6
  * defines a unified Worker interface that enables workflows to be designed
7
7
  * once and executed by any combination of AI and human workers.
8
8
  *
9
- * Package relationships:
9
+ * ## Worker Routing vs ai-functions Primitives
10
+ *
11
+ * **IMPORTANT:** This package exports functions that overlap in name with
12
+ * `ai-functions` primitives (do, ask, decide, approve, generate, is) but
13
+ * serve fundamentally different purposes:
14
+ *
15
+ * | Function | digital-workers | ai-functions |
16
+ * |----------|----------------|--------------|
17
+ * | `do` | Routes tasks to Workers | Direct LLM task description |
18
+ * | `ask` | Routes questions via Slack/email | LLM content for human UI |
19
+ * | `decide` | Multi-criteria decision framework | LLM-as-judge comparison |
20
+ * | `approve` | Real approval workflow via channels | LLM-generated approval content |
21
+ * | `generate` | Content generation with metadata | Core LLM generation primitive |
22
+ * | `is` | Type/schema validation with errors | Boolean assertion via LLM |
23
+ * | `notify` | Real channel delivery | (no equivalent) |
24
+ *
25
+ * **digital-workers functions are worker coordination primitives** that route
26
+ * work to AI Agents or Humans via real communication channels.
27
+ *
28
+ * **ai-functions primitives are direct LLM operations** for text generation,
29
+ * decision-making, and content creation.
30
+ *
31
+ * ## Package relationships:
10
32
  * - `autonomous-agents` - Implements Worker for AI agents
11
33
  * - `human-in-the-loop` - Implements Worker for humans
12
34
  * - `ai-workflows` - Uses digital-workers to orchestrate execution
@@ -32,12 +54,13 @@
32
54
  * const worker$ = withWorkers($)
33
55
  *
34
56
  * $.on.Expense.submitted(async (expense) => {
57
+ * // These route to REAL workers via REAL channels
35
58
  * await worker$.notify(finance, `New expense: ${expense.amount}`)
36
59
  *
37
60
  * const approval = await worker$.approve(
38
61
  * `Expense: $${expense.amount}`,
39
62
  * manager,
40
- * { via: 'slack' }
63
+ * { via: 'slack' } // Actually sends to Slack!
41
64
  * )
42
65
  *
43
66
  * if (approval.approved) {
@@ -72,13 +95,27 @@ export {
72
95
  export { Role } from './role.js'
73
96
  export { Team } from './team.js'
74
97
  export { Goals } from './goals.js'
98
+
99
+ /**
100
+ * Worker Routing Functions
101
+ *
102
+ * These functions route work to Workers (AI Agents or Humans) via real
103
+ * communication channels. They are NOT direct LLM primitives.
104
+ *
105
+ * For direct LLM primitives, use the identically-named functions from
106
+ * `ai-functions` instead. See module documentation for comparison table.
107
+ */
75
108
  export { approve } from './approve.js'
76
109
  export { ask } from './ask.js'
110
+ export { browse } from './browse.js'
77
111
  export { do } from './do.js'
78
112
  export { decide } from './decide.js'
79
113
  export { generate } from './generate.js'
114
+ export { image } from './image.js'
80
115
  export { is } from './is.js'
81
116
  export { notify } from './notify.js'
117
+ export { video } from './video.js'
118
+
82
119
  export { kpis, okrs } from './kpis.js'
83
120
 
84
121
  // Export verb definitions
@@ -114,6 +151,47 @@ export type {
114
151
  ProfileConstraints,
115
152
  } from './capability-tiers.js'
116
153
 
154
+ // Export browser automation types
155
+ export type {
156
+ BrowseOptions,
157
+ BrowseResult,
158
+ BrowseAction,
159
+ BrowseActionType,
160
+ Viewport,
161
+ ClickOptions,
162
+ TypeOptions,
163
+ ScrollOptions,
164
+ ScreenshotOptions,
165
+ ExtractOptions,
166
+ } from './browse.js'
167
+
168
+ // Export image generation types
169
+ export type {
170
+ ImageStyle,
171
+ ImageSize,
172
+ ImageFormat,
173
+ ImageOptions,
174
+ ImageResult,
175
+ VariationOptions,
176
+ EditOptions,
177
+ UpscaleOptions,
178
+ UpscaleResult,
179
+ } from './image.js'
180
+
181
+ // Export video generation types
182
+ export type {
183
+ VideoOptions,
184
+ VideoResult,
185
+ VideoResolution,
186
+ VideoAspectRatio,
187
+ VideoModel,
188
+ VideoStyle,
189
+ VideoMetadata,
190
+ VideoFromImageOptions,
191
+ VideoExtendOptions,
192
+ VideoEditOptions,
193
+ } from './video.js'
194
+
117
195
  // Export transport bridge (connects to digital-tools)
118
196
  export type {
119
197
  Transport,
@@ -274,6 +352,76 @@ export type {
274
352
  CompositeBalancerConfig,
275
353
  } from './load-balancing.js'
276
354
 
355
+ // Export Slack transport adapter
356
+ export {
357
+ SlackTransport,
358
+ createSlackTransport,
359
+ registerSlackTransport,
360
+ // Block Kit helpers
361
+ slackSection,
362
+ slackHeader,
363
+ slackDivider,
364
+ slackContext,
365
+ slackButton,
366
+ slackActions,
367
+ } from './transports/slack.js'
368
+
369
+ export type {
370
+ SlackTransportConfig,
371
+ SlackBlockType,
372
+ SlackTextObject,
373
+ SlackButtonElement,
374
+ SlackConfirmDialog,
375
+ SlackSectionBlock,
376
+ SlackDividerBlock,
377
+ SlackHeaderBlock,
378
+ SlackContextBlock,
379
+ SlackActionsBlock,
380
+ SlackBlock,
381
+ SlackMessage,
382
+ SlackApiResponse,
383
+ SlackPostMessageResponse,
384
+ SlackUserInfoResponse,
385
+ SlackConversationInfoResponse,
386
+ SlackInteractionPayload,
387
+ SlackActionPayload,
388
+ SlackWebhookRequest,
389
+ WebhookHandlerResult,
390
+ } from './transports/slack.js'
391
+
392
+ // Export Email transport adapter
393
+ export {
394
+ EmailTransport,
395
+ createEmailTransport,
396
+ createEmailTransportWithProvider,
397
+ createResendProvider,
398
+ // Template generators
399
+ generateNotificationEmail,
400
+ generateApprovalEmail,
401
+ // Reply parsing
402
+ parseApprovalReply,
403
+ // Type guards
404
+ isEmailTransportConfig,
405
+ isApproved,
406
+ isRejected,
407
+ } from './transports/email.js'
408
+
409
+ export type {
410
+ // Provider types
411
+ EmailProvider,
412
+ EmailMessage,
413
+ EmailSendResult,
414
+ EmailAttachment,
415
+ EmailTag,
416
+ // Configuration types
417
+ EmailTransportConfig,
418
+ EmailTemplateOptions,
419
+ // Approval types
420
+ ApprovalRequestData,
421
+ ParsedEmailReply,
422
+ InboundEmail,
423
+ } from './transports/email.js'
424
+
277
425
  // Export error escalation for multi-level error handling
278
426
  export {
279
427
  // Error Classification
@@ -339,3 +487,40 @@ export type {
339
487
  HandleErrorOptions,
340
488
  EscalationMetrics,
341
489
  } from './error-escalation.js'
490
+
491
+ // Export runtime integration for human request processing
492
+ export {
493
+ // Classes
494
+ HumanRequestProcessor,
495
+ InMemoryRequestStore,
496
+ // Factory functions
497
+ createHumanRequestProcessor,
498
+ } from './runtime.js'
499
+
500
+ export type {
501
+ // Request types
502
+ HumanRequest,
503
+ HumanRequestStore,
504
+ RequestStatus,
505
+ RequestType,
506
+ RequestResult,
507
+ CreateRequestData,
508
+ UpdateRequestData,
509
+ // Processor types
510
+ ProcessorConfig,
511
+ TransportAdapters,
512
+ SubmitResult,
513
+ SubmitRequestData,
514
+ WebhookPayload,
515
+ WebhookResult,
516
+ CompleteCallbackData,
517
+ TimeoutCallbackData,
518
+ CancelResult,
519
+ } from './runtime.js'
520
+
521
+ // Export logger interface for error logging
522
+ export type { Logger } from './logger.js'
523
+ export { noopLogger, createConsoleLogger } from './logger.js'
524
+
525
+ // Export ID generation utilities
526
+ export { generateRequestId } from './utils/id.js'
package/src/is.ts CHANGED
@@ -1,5 +1,29 @@
1
1
  /**
2
2
  * Type validation and checking functionality for digital workers
3
+ *
4
+ * IMPORTANT: Schema Validation vs Boolean Assertion
5
+ * --------------------------------------------------
6
+ * This module provides comprehensive type/schema validation,
7
+ * NOT simple boolean assertions.
8
+ *
9
+ * - `digital-workers.is()` - Validates values against types or schemas,
10
+ * returns detailed validation results with errors and optional coercion.
11
+ *
12
+ * - `ai-functions.is()` - Boolean assertion via LLM (e.g., `is\`${x} is valid\``)
13
+ * returns true/false based on natural language checking.
14
+ *
15
+ * Use digital-workers when you need:
16
+ * - Type validation with error messages
17
+ * - Schema validation with field-level errors
18
+ * - Value coercion (string to number, etc.)
19
+ * - Structured validation results
20
+ *
21
+ * Use ai-functions when you need:
22
+ * - Natural language boolean checks
23
+ * - LLM-based semantic validation
24
+ * - Template literal assertions
25
+ *
26
+ * @module
3
27
  */
4
28
 
5
29
  import { generateObject } from 'ai-functions'
@@ -7,26 +31,35 @@ import { schema as convertSchema, type SimpleSchema } from 'ai-functions'
7
31
  import type { TypeCheckResult, IsOptions } from './types.js'
8
32
 
9
33
  /**
10
- * Check if a value matches an expected type or schema
34
+ * Validate a value against a type or schema with detailed results.
11
35
  *
12
- * Uses AI-powered validation for complex types and schemas.
13
- * Can also perform type coercion when enabled.
36
+ * **Key Difference from ai-functions.is():**
37
+ * Unlike `ai-functions.is()` which is a boolean assertion using natural
38
+ * language (e.g., `is\`${email} is valid\`` returns true/false), this
39
+ * function performs structured type/schema validation and returns a
40
+ * `TypeCheckResult` with:
41
+ * - Validation status
42
+ * - Error messages for invalid fields
43
+ * - Optionally coerced values
44
+ *
45
+ * This is a **validation primitive**, not a boolean assertion primitive.
14
46
  *
15
47
  * @param value - The value to check
16
- * @param type - Type name or schema to validate against
17
- * @param options - Validation options
18
- * @returns Promise resolving to validation result
48
+ * @param type - Type name ('email', 'url', 'number') or schema to validate against
49
+ * @param options - Validation options (coerce, strict)
50
+ * @returns Promise resolving to TypeCheckResult with valid, value, and errors
19
51
  *
20
52
  * @example
21
53
  * ```ts
22
- * // Simple type checking
54
+ * // Simple type checking with result object
23
55
  * const result = await is('hello@example.com', 'email')
24
56
  * console.log(result.valid) // true
57
+ * console.log(result.errors) // undefined when valid
25
58
  * ```
26
59
  *
27
60
  * @example
28
61
  * ```ts
29
- * // Schema validation
62
+ * // Schema validation with detailed errors
30
63
  * const result = await is(
31
64
  * { name: 'John', age: 30 },
32
65
  * {
@@ -41,11 +74,13 @@ import type { TypeCheckResult, IsOptions } from './types.js'
41
74
  *
42
75
  * @example
43
76
  * ```ts
44
- * // With coercion
77
+ * // With coercion - transforms value to target type
45
78
  * const result = await is('123', 'number', { coerce: true })
46
79
  * console.log(result.valid) // true
47
- * console.log(result.value) // 123 (as number)
80
+ * console.log(result.value) // 123 (as number, not string)
48
81
  * ```
82
+ *
83
+ * @see {@link ai-functions#is} for natural language boolean assertions
49
84
  */
50
85
  export async function is(
51
86
  value: unknown,
@@ -102,8 +137,7 @@ async function validateSimpleType(
102
137
 
103
138
  return {
104
139
  valid: isValid,
105
- value: isValid ? value : undefined,
106
- errors: isValid ? undefined : [`Value is not a valid ${type}`],
140
+ ...(isValid ? { value } : { errors: [`Value is not a valid ${type}`] }),
107
141
  }
108
142
  }
109
143
 
@@ -118,7 +152,11 @@ async function validateSimpleType(
118
152
  system: `You are a type validation expert. Determine if a value matches an expected type.
119
153
 
120
154
  ${coerce ? 'If the value can be coerced to the expected type, provide the coerced value.' : ''}
121
- ${strict ? 'Be strict in your validation - require exact type matches.' : 'Be flexible - allow reasonable type conversions.'}`,
155
+ ${
156
+ strict
157
+ ? 'Be strict in your validation - require exact type matches.'
158
+ : 'Be flexible - allow reasonable type conversions.'
159
+ }`,
122
160
  prompt: `Validate if this value matches the expected type:
123
161
 
124
162
  Value: ${JSON.stringify(value)}
@@ -136,7 +174,7 @@ Determine if the value is valid for this type.`,
136
174
  return {
137
175
  valid: validation.valid,
138
176
  value: coerce && validation.coercedValue !== undefined ? validation.coercedValue : value,
139
- errors: validation.valid ? undefined : validation.errors,
177
+ ...(!validation.valid && { errors: validation.errors }),
140
178
  }
141
179
  }
142
180
 
@@ -201,7 +239,7 @@ Check if the value matches the schema structure and types.`,
201
239
  return {
202
240
  valid: validation.valid,
203
241
  value: coerce && validation.coercedValue !== undefined ? validation.coercedValue : value,
204
- errors: validation.valid ? undefined : validation.errors,
242
+ ...(!validation.valid && { errors: validation.errors }),
205
243
  }
206
244
  }
207
245
  }
@@ -209,10 +247,7 @@ Check if the value matches the schema structure and types.`,
209
247
  /**
210
248
  * Try to coerce a value to a specific type
211
249
  */
212
- function coerceValue(
213
- value: unknown,
214
- type: string
215
- ): { success: boolean; value?: unknown } {
250
+ function coerceValue(value: unknown, type: string): { success: boolean; value?: unknown } {
216
251
  try {
217
252
  switch (type) {
218
253
  case 'string':
@@ -266,8 +301,7 @@ is.email = async (value: unknown): Promise<TypeCheckResult> => {
266
301
 
267
302
  return {
268
303
  valid,
269
- value: valid ? value : undefined,
270
- errors: valid ? undefined : ['Invalid email format'],
304
+ ...(valid ? { value } : { errors: ['Invalid email format'] }),
271
305
  }
272
306
  }
273
307
 
@@ -310,10 +344,11 @@ is.date = async (value: unknown, options: IsOptions = {}): Promise<TypeCheckResu
310
344
  const { coerce } = options
311
345
 
312
346
  if (value instanceof Date) {
347
+ const isValid = !isNaN(value.getTime())
313
348
  return {
314
- valid: !isNaN(value.getTime()),
349
+ valid: isValid,
315
350
  value,
316
- errors: isNaN(value.getTime()) ? ['Invalid date'] : undefined,
351
+ ...(!isValid && { errors: ['Invalid date'] }),
317
352
  }
318
353
  }
319
354
 
@@ -360,8 +395,7 @@ is.custom = async (
360
395
  const valid = await validator(value)
361
396
  return {
362
397
  valid,
363
- value: valid ? value : undefined,
364
- errors: valid ? undefined : ['Custom validation failed'],
398
+ ...(valid ? { value } : { errors: ['Custom validation failed'] }),
365
399
  }
366
400
  } catch (error) {
367
401
  return {
package/src/kpis.ts CHANGED
@@ -2,11 +2,20 @@
2
2
  * KPI and OKR tracking functionality for digital workers
3
3
  */
4
4
 
5
- import type { KPI, OKR } from './types.js'
5
+ import type { KPI, OKR, KeyResult } from 'org.ai'
6
+ import { calculateProgress, isOnTrack, calculateGap } from 'org.ai'
7
+ import type { WorkerKPI, WorkerOKR } from './types.js'
8
+
9
+ // Re-export KPI, OKR from org.ai for convenience
10
+ export type { KPI, OKR, KeyResult } from 'org.ai'
6
11
 
7
12
  /**
8
13
  * Define and track Key Performance Indicators
9
14
  *
15
+ * Uses WorkerKPI which has a simpler interface with `current` and `target`
16
+ * as required number fields. For the full org.ai KPI with `id`, `value`,
17
+ * `category`, and `history`, use the KPI type directly.
18
+ *
10
19
  * @param definition - KPI definition or array of KPIs
11
20
  * @returns The defined KPI(s)
12
21
  *
@@ -46,9 +55,9 @@ import type { KPI, OKR } from './types.js'
46
55
  * ])
47
56
  * ```
48
57
  */
49
- export function kpis(definition: KPI): KPI
50
- export function kpis(definition: KPI[]): KPI[]
51
- export function kpis(definition: KPI | KPI[]): KPI | KPI[] {
58
+ export function kpis(definition: WorkerKPI): WorkerKPI
59
+ export function kpis(definition: WorkerKPI[]): WorkerKPI[]
60
+ export function kpis(definition: WorkerKPI | WorkerKPI[]): WorkerKPI | WorkerKPI[] {
52
61
  return definition
53
62
  }
54
63
 
@@ -66,9 +75,9 @@ export function kpis(definition: KPI | KPI[]): KPI | KPI[] {
66
75
  * console.log(updated.trend) // 'up' (automatically determined)
67
76
  * ```
68
77
  */
69
- kpis.update = (kpi: KPI, current: number): KPI => {
78
+ kpis.update = (kpi: WorkerKPI, current: number): WorkerKPI => {
70
79
  // Determine trend
71
- let trend: KPI['trend'] = 'stable'
80
+ let trend: WorkerKPI['trend'] = 'stable'
72
81
  if (current > kpi.current) {
73
82
  trend = 'up'
74
83
  } else if (current < kpi.current) {
@@ -94,9 +103,8 @@ kpis.update = (kpi: KPI, current: number): KPI => {
94
103
  * const progress = kpis.progress(kpi) // 0.75
95
104
  * ```
96
105
  */
97
- kpis.progress = (kpi: Pick<KPI, 'current' | 'target'>): number => {
98
- if (kpi.target === 0) return 0
99
- return Math.min(1, Math.max(0, kpi.current / kpi.target))
106
+ kpis.progress = (kpi: Pick<WorkerKPI, 'current' | 'target'>): number => {
107
+ return calculateProgress(kpi)
100
108
  }
101
109
 
102
110
  /**
@@ -112,8 +120,8 @@ kpis.progress = (kpi: Pick<KPI, 'current' | 'target'>): number => {
112
120
  * const onTrack = kpis.onTrack(kpi) // true (85% >= 80%)
113
121
  * ```
114
122
  */
115
- kpis.onTrack = (kpi: Pick<KPI, 'current' | 'target'>, threshold = 0.8): boolean => {
116
- return kpis.progress(kpi) >= threshold
123
+ kpis.onTrack = (kpi: Pick<WorkerKPI, 'current' | 'target'>, threshold = 0.8): boolean => {
124
+ return isOnTrack(kpi, threshold)
117
125
  }
118
126
 
119
127
  /**
@@ -128,8 +136,8 @@ kpis.onTrack = (kpi: Pick<KPI, 'current' | 'target'>, threshold = 0.8): boolean
128
136
  * const gap = kpis.gap(kpi) // 25
129
137
  * ```
130
138
  */
131
- kpis.gap = (kpi: Pick<KPI, 'current' | 'target'>): number => {
132
- return kpi.target - kpi.current
139
+ kpis.gap = (kpi: Pick<WorkerKPI, 'current' | 'target'>): number => {
140
+ return calculateGap(kpi)
133
141
  }
134
142
 
135
143
  /**
@@ -151,7 +159,7 @@ kpis.gap = (kpi: Pick<KPI, 'current' | 'target'>): number => {
151
159
  * // "Deployment Frequency: 5/10 deploys/week (50%, trending up)"
152
160
  * ```
153
161
  */
154
- kpis.format = (kpi: KPI): string => {
162
+ kpis.format = (kpi: WorkerKPI): string => {
155
163
  const progress = kpis.progress(kpi)
156
164
  const progressPercent = Math.round(progress * 100)
157
165
  const trendEmoji = kpi.trend === 'up' ? '↑' : kpi.trend === 'down' ? '↓' : '→'
@@ -175,17 +183,15 @@ kpis.format = (kpi: KPI): string => {
175
183
  * ```
176
184
  */
177
185
  kpis.compare = (
178
- previous: Pick<KPI, 'current' | 'target'>,
179
- current: Pick<KPI, 'current' | 'target'>
186
+ previous: Pick<WorkerKPI, 'current' | 'target'>,
187
+ current: Pick<WorkerKPI, 'current' | 'target'>
180
188
  ): {
181
189
  delta: number
182
190
  percentChange: number
183
191
  improved: boolean
184
192
  } => {
185
193
  const delta = current.current - previous.current
186
- const percentChange = previous.current !== 0
187
- ? (delta / previous.current) * 100
188
- : 0
194
+ const percentChange = previous.current !== 0 ? (delta / previous.current) * 100 : 0
189
195
 
190
196
  // Improved if we got closer to the target
191
197
  const previousGap = Math.abs(previous.target - previous.current)
@@ -202,6 +208,10 @@ kpis.compare = (
202
208
  /**
203
209
  * Define OKRs (Objectives and Key Results)
204
210
  *
211
+ * Uses WorkerOKR which has WorkerRef for owner.
212
+ * For the full org.ai OKR with `id`, `status`, `period`, etc.,
213
+ * use the OKR type directly.
214
+ *
205
215
  * @param definition - OKR definition
206
216
  * @returns The defined OKR
207
217
  *
@@ -229,12 +239,12 @@ kpis.compare = (
229
239
  * unit: '%',
230
240
  * },
231
241
  * ],
232
- * owner: 'engineering-team',
242
+ * owner: { id: 'engineering-team', type: 'agent' },
233
243
  * dueDate: new Date('2024-03-31'),
234
244
  * })
235
245
  * ```
236
246
  */
237
- export function okrs(definition: OKR): OKR {
247
+ export function okrs(definition: WorkerOKR): WorkerOKR {
238
248
  return definition
239
249
  }
240
250
 
@@ -250,7 +260,7 @@ export function okrs(definition: OKR): OKR {
250
260
  * console.log(progress) // 0.67 (67% complete)
251
261
  * ```
252
262
  */
253
- okrs.progress = (okr: OKR): number => {
263
+ okrs.progress = (okr: WorkerOKR): number => {
254
264
  if (okr.keyResults.length === 0) return 0
255
265
 
256
266
  const totalProgress = okr.keyResults.reduce((sum, kr) => {
@@ -277,17 +287,11 @@ okrs.progress = (okr: OKR): number => {
277
287
  * )
278
288
  * ```
279
289
  */
280
- okrs.updateKeyResult = (
281
- okr: OKR,
282
- keyResultName: string,
283
- current: number
284
- ): OKR => {
290
+ okrs.updateKeyResult = (okr: WorkerOKR, keyResultName: string, current: number): WorkerOKR => {
291
+ const { progress: _progress, ...rest } = okr
285
292
  return {
286
- ...okr,
287
- keyResults: okr.keyResults.map((kr) =>
288
- kr.name === keyResultName ? { ...kr, current } : kr
289
- ),
290
- progress: undefined, // Will be recalculated
293
+ ...rest,
294
+ keyResults: okr.keyResults.map((kr) => (kr.name === keyResultName ? { ...kr, current } : kr)),
291
295
  }
292
296
  }
293
297
 
@@ -303,7 +307,7 @@ okrs.updateKeyResult = (
303
307
  * const onTrack = okrs.onTrack(engineeringOKR)
304
308
  * ```
305
309
  */
306
- okrs.onTrack = (okr: OKR, threshold = 0.7): boolean => {
310
+ okrs.onTrack = (okr: WorkerOKR, threshold = 0.7): boolean => {
307
311
  return okrs.progress(okr) >= threshold
308
312
  }
309
313
 
@@ -323,7 +327,7 @@ okrs.onTrack = (okr: OKR, threshold = 0.7): boolean => {
323
327
  * // • Change Failure Rate: 15/5 % (300%)
324
328
  * ```
325
329
  */
326
- okrs.format = (okr: OKR): string => {
330
+ okrs.format = (okr: WorkerOKR): string => {
327
331
  const progress = okrs.progress(okr)
328
332
  const progressPercent = Math.round(progress * 100)
329
333
 
@@ -332,12 +336,13 @@ okrs.format = (okr: OKR): string => {
332
336
  ...okr.keyResults.map((kr) => {
333
337
  const krProgress = kpis.progress(kr)
334
338
  const krPercent = Math.round(krProgress * 100)
335
- return ` ${kr.name}: ${kr.current}/${kr.target} ${kr.unit} (${krPercent}%)`
339
+ return ` - ${kr.name}: ${kr.current}/${kr.target} ${kr.unit} (${krPercent}%)`
336
340
  }),
337
341
  ]
338
342
 
339
343
  if (okr.owner) {
340
- lines.push(` Owner: ${okr.owner}`)
344
+ const ownerDisplay = typeof okr.owner === 'string' ? okr.owner : okr.owner.id
345
+ lines.push(` Owner: ${ownerDisplay}`)
341
346
  }
342
347
 
343
348
  if (okr.dueDate) {