digital-workers 2.1.3 → 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.
- package/CHANGELOG.md +9 -0
- package/README.md +2 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +33 -21
- package/dist/actions.js.map +1 -1
- package/dist/agent-comms.d.ts.map +1 -1
- package/dist/agent-comms.js +36 -25
- package/dist/agent-comms.js.map +1 -1
- package/dist/approve.d.ts +40 -8
- package/dist/approve.d.ts.map +1 -1
- package/dist/approve.js +86 -20
- package/dist/approve.js.map +1 -1
- package/dist/ask.d.ts +38 -7
- package/dist/ask.d.ts.map +1 -1
- package/dist/ask.js +85 -25
- package/dist/ask.js.map +1 -1
- package/dist/browse.d.ts +223 -0
- package/dist/browse.d.ts.map +1 -0
- package/dist/browse.js +392 -0
- package/dist/browse.js.map +1 -0
- package/dist/capability-tiers.js +3 -3
- package/dist/capability-tiers.js.map +1 -1
- package/dist/cascade-context.d.ts +28 -28
- package/dist/client.d.ts +162 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +64 -0
- package/dist/client.js.map +1 -0
- package/dist/decide.d.ts +42 -6
- package/dist/decide.d.ts.map +1 -1
- package/dist/decide.js +54 -11
- package/dist/decide.js.map +1 -1
- package/dist/do.d.ts +36 -7
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +82 -39
- package/dist/do.js.map +1 -1
- package/dist/error-escalation.d.ts.map +1 -1
- package/dist/error-escalation.js +38 -38
- package/dist/error-escalation.js.map +1 -1
- package/dist/generate.d.ts +48 -7
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +49 -8
- package/dist/generate.js.map +1 -1
- package/dist/goals.d.ts +10 -9
- package/dist/goals.d.ts.map +1 -1
- package/dist/goals.js +30 -24
- package/dist/goals.js.map +1 -1
- package/dist/image.d.ts +189 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +528 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +49 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -2
- package/dist/index.js.map +1 -1
- package/dist/is.d.ts +45 -10
- package/dist/is.d.ts.map +1 -1
- package/dist/is.js +56 -21
- package/dist/is.js.map +1 -1
- package/dist/kpis.d.ts +24 -15
- package/dist/kpis.d.ts.map +1 -1
- package/dist/kpis.js +16 -14
- package/dist/kpis.js.map +1 -1
- package/dist/load-balancing.d.ts.map +1 -1
- package/dist/load-balancing.js +124 -38
- package/dist/load-balancing.js.map +1 -1
- package/dist/logger.d.ts +76 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +39 -0
- package/dist/logger.js.map +1 -0
- package/dist/notify.d.ts +38 -9
- package/dist/notify.d.ts.map +1 -1
- package/dist/notify.js +72 -17
- package/dist/notify.js.map +1 -1
- package/dist/role.d.ts +5 -4
- package/dist/role.d.ts.map +1 -1
- package/dist/role.js +13 -10
- package/dist/role.js.map +1 -1
- package/dist/runtime.d.ts +310 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +510 -0
- package/dist/runtime.js.map +1 -0
- package/dist/team.d.ts +11 -6
- package/dist/team.d.ts.map +1 -1
- package/dist/team.js +22 -15
- package/dist/team.js.map +1 -1
- package/dist/transports/email.d.ts +318 -0
- package/dist/transports/email.d.ts.map +1 -0
- package/dist/transports/email.js +779 -0
- package/dist/transports/email.js.map +1 -0
- package/dist/transports/slack.d.ts +515 -0
- package/dist/transports/slack.d.ts.map +1 -0
- package/dist/transports/slack.js +844 -0
- package/dist/transports/slack.js.map +1 -0
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +44 -25
- package/dist/transports.js.map +1 -1
- package/dist/types.d.ts +141 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/id.d.ts +19 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +21 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/video.d.ts +203 -0
- package/dist/video.d.ts.map +1 -0
- package/dist/video.js +528 -0
- package/dist/video.js.map +1 -0
- package/dist/worker.d.ts +343 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +698 -0
- package/dist/worker.js.map +1 -0
- package/package.json +32 -14
- package/src/actions.ts +39 -30
- package/src/agent-comms.ts +54 -92
- package/src/approve.ts +91 -20
- package/src/ask.ts +99 -25
- package/src/browse.ts +627 -0
- package/src/capability-tiers.ts +5 -5
- package/src/client.ts +221 -0
- package/src/decide.ts +81 -35
- package/src/do.ts +98 -52
- package/src/error-escalation.ts +55 -67
- package/src/generate.ts +52 -18
- package/src/goals.ts +36 -27
- package/src/image.ts +816 -0
- package/src/index.ts +187 -2
- package/src/is.ts +59 -25
- package/src/kpis.ts +41 -36
- package/src/load-balancing.ts +132 -46
- package/src/logger.ts +93 -0
- package/src/notify.ts +78 -17
- package/src/role.ts +30 -20
- package/src/runtime.ts +796 -0
- package/src/team.ts +24 -19
- package/src/transports/email.ts +1160 -0
- package/src/transports/slack.ts +1320 -0
- package/src/transports.ts +58 -43
- package/src/types.ts +174 -46
- package/src/utils/id.ts +21 -0
- package/src/video.ts +906 -0
- package/src/worker.ts +1007 -0
- package/test/approve.test.ts +305 -0
- package/test/ask.test.ts +274 -0
- package/test/browse.test.ts +361 -0
- package/test/decide.test.ts +252 -0
- package/test/do.test.ts +144 -0
- package/test/error-logging.test.ts +357 -0
- package/test/generate.test.ts +319 -0
- package/test/image.test.ts +398 -0
- package/test/is.test.ts +287 -0
- package/test/load-balancing-safety.test.ts +404 -0
- package/test/notify.test.ts +434 -0
- package/test/primitives.test.ts +320 -0
- package/test/runtime-integration.test.ts +892 -0
- package/test/transports/crypto.test.ts +230 -0
- package/test/transports/email.test.ts +866 -0
- package/test/transports/id-generation.test.ts +91 -0
- package/test/transports/slack.test.ts +760 -0
- package/test/type-safety.test.ts +834 -0
- package/test/types.test.ts +60 -2
- package/test/video.test.ts +530 -0
- package/test/worker.test.ts +1433 -0
- package/tsconfig.json +4 -1
- package/vitest.config.ts +42 -0
- package/wrangler.jsonc +36 -0
- package/.turbo/turbo-build.log +0 -4
- package/LICENSE +0 -21
- package/src/actions.js +0 -436
- package/src/approve.js +0 -234
- package/src/ask.js +0 -226
- package/src/decide.js +0 -244
- package/src/do.js +0 -227
- package/src/generate.js +0 -298
- package/src/goals.js +0 -205
- package/src/index.js +0 -68
- package/src/is.js +0 -317
- package/src/kpis.js +0 -270
- package/src/notify.js +0 -219
- package/src/role.js +0 -110
- package/src/team.js +0 -130
- package/src/transports.js +0 -357
- package/src/types.js +0 -71
package/src/load-balancing.ts
CHANGED
|
@@ -167,6 +167,38 @@ export interface MetricsCollector {
|
|
|
167
167
|
reset(): void
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
// ============================================================================
|
|
171
|
+
// Safe Array Access Utilities
|
|
172
|
+
// ============================================================================
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Safely get the first element of an array.
|
|
176
|
+
*
|
|
177
|
+
* Provides type-safe access to array elements without non-null assertions.
|
|
178
|
+
*
|
|
179
|
+
* @param arr - The array to access
|
|
180
|
+
* @returns The first element or undefined if array is empty
|
|
181
|
+
*/
|
|
182
|
+
function safeFirst<T>(arr: T[]): T | undefined {
|
|
183
|
+
return arr.length > 0 ? arr[0] : undefined
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Safely get an element at a specific index.
|
|
188
|
+
*
|
|
189
|
+
* Provides type-safe access with bounds checking.
|
|
190
|
+
*
|
|
191
|
+
* @param arr - The array to access
|
|
192
|
+
* @param index - The index to access
|
|
193
|
+
* @returns The element at the index or undefined if out of bounds
|
|
194
|
+
*/
|
|
195
|
+
function safeAt<T>(arr: T[], index: number): T | undefined {
|
|
196
|
+
if (arr.length === 0 || index < 0 || index >= arr.length) {
|
|
197
|
+
return undefined
|
|
198
|
+
}
|
|
199
|
+
return arr[index]
|
|
200
|
+
}
|
|
201
|
+
|
|
170
202
|
// ============================================================================
|
|
171
203
|
// MetricsCollector Implementation
|
|
172
204
|
// ============================================================================
|
|
@@ -213,11 +245,7 @@ export function createMetricsCollector(): MetricsCollector {
|
|
|
213
245
|
}
|
|
214
246
|
let totalLatency = 0
|
|
215
247
|
|
|
216
|
-
function record(
|
|
217
|
-
result: RouteResult,
|
|
218
|
-
latencyMs: number,
|
|
219
|
-
strategy: BalancerStrategy
|
|
220
|
-
): void {
|
|
248
|
+
function record(result: RouteResult, latencyMs: number, strategy: BalancerStrategy): void {
|
|
221
249
|
metricsState.totalRouted++
|
|
222
250
|
totalLatency += latencyMs
|
|
223
251
|
metricsState.averageLatencyMs = totalLatency / metricsState.totalRouted
|
|
@@ -326,7 +354,7 @@ export function createRoundRobinBalancer(
|
|
|
326
354
|
const collector = options.metricsCollector ?? defaultMetricsCollector
|
|
327
355
|
|
|
328
356
|
function getAvailableAgents(): AgentInfo[] {
|
|
329
|
-
return agents.filter(a => a.status === 'available' || a.status === 'busy')
|
|
357
|
+
return agents.filter((a) => a.status === 'available' || a.status === 'busy')
|
|
330
358
|
}
|
|
331
359
|
|
|
332
360
|
function route(task: TaskRequest): RouteResult {
|
|
@@ -348,8 +376,12 @@ export function createRoundRobinBalancer(
|
|
|
348
376
|
// Find the next available agent starting from current index
|
|
349
377
|
let attempts = 0
|
|
350
378
|
while (attempts < agents.length) {
|
|
351
|
-
const agent = agents
|
|
379
|
+
const agent = safeAt(agents, currentIndex % agents.length)
|
|
352
380
|
currentIndex++
|
|
381
|
+
if (!agent) {
|
|
382
|
+
attempts++
|
|
383
|
+
continue
|
|
384
|
+
}
|
|
353
385
|
|
|
354
386
|
if (agent.status === 'available' || agent.status === 'busy') {
|
|
355
387
|
const result: RouteResult = {
|
|
@@ -380,7 +412,7 @@ export function createRoundRobinBalancer(
|
|
|
380
412
|
}
|
|
381
413
|
|
|
382
414
|
function removeAgent(agentId: string): void {
|
|
383
|
-
agents = agents.filter(a => a.id !== agentId)
|
|
415
|
+
agents = agents.filter((a) => a.id !== agentId)
|
|
384
416
|
}
|
|
385
417
|
|
|
386
418
|
function getAgents(): AgentInfo[] {
|
|
@@ -430,10 +462,10 @@ export function createLeastBusyBalancer(
|
|
|
430
462
|
const collector = options.metricsCollector ?? defaultMetricsCollector
|
|
431
463
|
|
|
432
464
|
// Initialize load tracking
|
|
433
|
-
agents.forEach(a => loadTracking.set(a.id, a.currentLoad))
|
|
465
|
+
agents.forEach((a) => loadTracking.set(a.id, a.currentLoad))
|
|
434
466
|
|
|
435
467
|
function getAvailableAgents(): AgentInfo[] {
|
|
436
|
-
return agents.filter(a => {
|
|
468
|
+
return agents.filter((a) => {
|
|
437
469
|
if (a.status !== 'available' && a.status !== 'busy') return false
|
|
438
470
|
const load = loadTracking.get(a.id) ?? a.currentLoad
|
|
439
471
|
return load < a.maxLoad
|
|
@@ -471,7 +503,18 @@ export function createLeastBusyBalancer(
|
|
|
471
503
|
return loadA - loadB
|
|
472
504
|
})
|
|
473
505
|
|
|
474
|
-
const selected = sorted
|
|
506
|
+
const selected = safeFirst(sorted)
|
|
507
|
+
if (!selected) {
|
|
508
|
+
const result: RouteResult = {
|
|
509
|
+
agent: null,
|
|
510
|
+
task,
|
|
511
|
+
strategy: 'least-busy',
|
|
512
|
+
timestamp: new Date(),
|
|
513
|
+
reason: 'no-available-agents',
|
|
514
|
+
}
|
|
515
|
+
collector.record(result, performance.now() - start, 'least-busy')
|
|
516
|
+
return result
|
|
517
|
+
}
|
|
475
518
|
lastRoutedIndex = agents.indexOf(selected)
|
|
476
519
|
|
|
477
520
|
// Increment load
|
|
@@ -494,7 +537,7 @@ export function createLeastBusyBalancer(
|
|
|
494
537
|
}
|
|
495
538
|
|
|
496
539
|
function removeAgent(agentId: string): void {
|
|
497
|
-
agents = agents.filter(a => a.id !== agentId)
|
|
540
|
+
agents = agents.filter((a) => a.id !== agentId)
|
|
498
541
|
loadTracking.delete(agentId)
|
|
499
542
|
}
|
|
500
543
|
|
|
@@ -504,7 +547,7 @@ export function createLeastBusyBalancer(
|
|
|
504
547
|
|
|
505
548
|
function getLoadMetrics(): Record<string, number> {
|
|
506
549
|
const metrics: Record<string, number> = {}
|
|
507
|
-
agents.forEach(a => {
|
|
550
|
+
agents.forEach((a) => {
|
|
508
551
|
const load = loadTracking.get(a.id) ?? a.currentLoad
|
|
509
552
|
metrics[a.id] = load / a.maxLoad
|
|
510
553
|
})
|
|
@@ -566,16 +609,16 @@ export function createCapabilityRouter(
|
|
|
566
609
|
const collector = options.metricsCollector ?? defaultMetricsCollector
|
|
567
610
|
|
|
568
611
|
function getAvailableAgents(): AgentInfo[] {
|
|
569
|
-
return agents.filter(a => a.status === 'available' || a.status === 'busy')
|
|
612
|
+
return agents.filter((a) => a.status === 'available' || a.status === 'busy')
|
|
570
613
|
}
|
|
571
614
|
|
|
572
615
|
function hasAllSkills(agent: AgentInfo, requiredSkills: string[]): boolean {
|
|
573
|
-
return requiredSkills.every(skill => agent.skills.includes(skill))
|
|
616
|
+
return requiredSkills.every((skill) => agent.skills.includes(skill))
|
|
574
617
|
}
|
|
575
618
|
|
|
576
619
|
function calculateMatchScore(agent: AgentInfo, requiredSkills: string[]): number {
|
|
577
620
|
if (requiredSkills.length === 0) return 1
|
|
578
|
-
const matchingSkills = requiredSkills.filter(s => agent.skills.includes(s))
|
|
621
|
+
const matchingSkills = requiredSkills.filter((s) => agent.skills.includes(s))
|
|
579
622
|
return matchingSkills.length / requiredSkills.length
|
|
580
623
|
}
|
|
581
624
|
|
|
@@ -584,7 +627,7 @@ export function createCapabilityRouter(
|
|
|
584
627
|
const available = getAvailableAgents()
|
|
585
628
|
|
|
586
629
|
// Find agents with all required skills
|
|
587
|
-
let candidates = available.filter(a => hasAllSkills(a, task.requiredSkills))
|
|
630
|
+
let candidates = available.filter((a) => hasAllSkills(a, task.requiredSkills))
|
|
588
631
|
|
|
589
632
|
if (candidates.length === 0) {
|
|
590
633
|
const result: RouteResult = {
|
|
@@ -608,7 +651,18 @@ export function createCapabilityRouter(
|
|
|
608
651
|
})
|
|
609
652
|
}
|
|
610
653
|
|
|
611
|
-
const selected = candidates
|
|
654
|
+
const selected = safeFirst(candidates)
|
|
655
|
+
if (!selected) {
|
|
656
|
+
const result: RouteResult = {
|
|
657
|
+
agent: null,
|
|
658
|
+
task,
|
|
659
|
+
strategy: 'capability',
|
|
660
|
+
timestamp: new Date(),
|
|
661
|
+
reason: 'no-matching-capability',
|
|
662
|
+
}
|
|
663
|
+
collector.record(result, performance.now() - start, 'capability')
|
|
664
|
+
return result
|
|
665
|
+
}
|
|
612
666
|
const matchScore = calculateMatchScore(selected, task.requiredSkills)
|
|
613
667
|
|
|
614
668
|
const result: RouteResult = {
|
|
@@ -627,7 +681,7 @@ export function createCapabilityRouter(
|
|
|
627
681
|
}
|
|
628
682
|
|
|
629
683
|
function removeAgent(agentId: string): void {
|
|
630
|
-
agents = agents.filter(a => a.id !== agentId)
|
|
684
|
+
agents = agents.filter((a) => a.id !== agentId)
|
|
631
685
|
}
|
|
632
686
|
|
|
633
687
|
function getAgents(): AgentInfo[] {
|
|
@@ -635,13 +689,13 @@ export function createCapabilityRouter(
|
|
|
635
689
|
}
|
|
636
690
|
|
|
637
691
|
function findAgentsWithSkills(skills: string[]): AgentInfo[] {
|
|
638
|
-
return agents.filter(a => hasAllSkills(a, skills))
|
|
692
|
+
return agents.filter((a) => hasAllSkills(a, skills))
|
|
639
693
|
}
|
|
640
694
|
|
|
641
695
|
function getSkillCoverage(): Record<string, number> {
|
|
642
696
|
const coverage: Record<string, number> = {}
|
|
643
|
-
agents.forEach(a => {
|
|
644
|
-
a.skills.forEach(skill => {
|
|
697
|
+
agents.forEach((a) => {
|
|
698
|
+
a.skills.forEach((skill) => {
|
|
645
699
|
coverage[skill] = (coverage[skill] || 0) + 1
|
|
646
700
|
})
|
|
647
701
|
})
|
|
@@ -706,11 +760,11 @@ export function createPriorityQueueBalancer(
|
|
|
706
760
|
const collector = options.metricsCollector ?? defaultMetricsCollector
|
|
707
761
|
|
|
708
762
|
function getAvailableAgents(): AgentInfo[] {
|
|
709
|
-
return agents.filter(a => a.status === 'available' || a.status === 'busy')
|
|
763
|
+
return agents.filter((a) => a.status === 'available' || a.status === 'busy')
|
|
710
764
|
}
|
|
711
765
|
|
|
712
766
|
function getEffectivePriority(taskId: string): number {
|
|
713
|
-
const task = queue.find(t => t.id === taskId)
|
|
767
|
+
const task = queue.find((t) => t.id === taskId)
|
|
714
768
|
if (!task) return 0
|
|
715
769
|
|
|
716
770
|
let priority = task.priority
|
|
@@ -758,7 +812,8 @@ export function createPriorityQueueBalancer(
|
|
|
758
812
|
if (queue.length === 0) return null
|
|
759
813
|
|
|
760
814
|
sortQueue()
|
|
761
|
-
const task = queue.shift()
|
|
815
|
+
const task = queue.shift()
|
|
816
|
+
if (!task) return null
|
|
762
817
|
const start = performance.now()
|
|
763
818
|
const available = getAvailableAgents()
|
|
764
819
|
|
|
@@ -775,7 +830,18 @@ export function createPriorityQueueBalancer(
|
|
|
775
830
|
}
|
|
776
831
|
|
|
777
832
|
// Simple round-robin among available for now
|
|
778
|
-
const agent = available
|
|
833
|
+
const agent = safeFirst(available)
|
|
834
|
+
if (!agent) {
|
|
835
|
+
const result: RouteResult = {
|
|
836
|
+
agent: null,
|
|
837
|
+
task,
|
|
838
|
+
strategy: 'priority-queue',
|
|
839
|
+
timestamp: new Date(),
|
|
840
|
+
reason: 'no-available-agents',
|
|
841
|
+
}
|
|
842
|
+
collector.record(result, performance.now() - start, 'priority-queue')
|
|
843
|
+
return result
|
|
844
|
+
}
|
|
779
845
|
|
|
780
846
|
const result: RouteResult = {
|
|
781
847
|
agent,
|
|
@@ -803,7 +869,18 @@ export function createPriorityQueueBalancer(
|
|
|
803
869
|
return result
|
|
804
870
|
}
|
|
805
871
|
|
|
806
|
-
const agent = available
|
|
872
|
+
const agent = safeFirst(available)
|
|
873
|
+
if (!agent) {
|
|
874
|
+
const result: RouteResult = {
|
|
875
|
+
agent: null,
|
|
876
|
+
task,
|
|
877
|
+
strategy: 'priority-queue',
|
|
878
|
+
timestamp: new Date(),
|
|
879
|
+
reason: 'no-available-agents',
|
|
880
|
+
}
|
|
881
|
+
collector.record(result, performance.now() - start, 'priority-queue')
|
|
882
|
+
return result
|
|
883
|
+
}
|
|
807
884
|
const result: RouteResult = {
|
|
808
885
|
agent,
|
|
809
886
|
task,
|
|
@@ -819,7 +896,7 @@ export function createPriorityQueueBalancer(
|
|
|
819
896
|
}
|
|
820
897
|
|
|
821
898
|
function removeAgent(agentId: string): void {
|
|
822
|
-
agents = agents.filter(a => a.id !== agentId)
|
|
899
|
+
agents = agents.filter((a) => a.id !== agentId)
|
|
823
900
|
}
|
|
824
901
|
|
|
825
902
|
function getAgents(): AgentInfo[] {
|
|
@@ -903,7 +980,7 @@ export function createAgentAvailabilityTracker(
|
|
|
903
980
|
const { heartbeatTimeout = 30000 } = options
|
|
904
981
|
|
|
905
982
|
// Initialize
|
|
906
|
-
initialAgents.forEach(a => {
|
|
983
|
+
initialAgents.forEach((a) => {
|
|
907
984
|
agents.set(a.id, a)
|
|
908
985
|
availability.set(a.id, {
|
|
909
986
|
status: a.status,
|
|
@@ -914,10 +991,12 @@ export function createAgentAvailabilityTracker(
|
|
|
914
991
|
})
|
|
915
992
|
|
|
916
993
|
function getAvailability(agentId: string): AgentAvailability {
|
|
917
|
-
return
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
994
|
+
return (
|
|
995
|
+
availability.get(agentId) ?? {
|
|
996
|
+
status: 'offline',
|
|
997
|
+
lastSeen: new Date(0),
|
|
998
|
+
}
|
|
999
|
+
)
|
|
921
1000
|
}
|
|
922
1001
|
|
|
923
1002
|
function updateStatus(agentId: string, status: WorkerStatus): void {
|
|
@@ -943,12 +1022,12 @@ export function createAgentAvailabilityTracker(
|
|
|
943
1022
|
currentStatus: status,
|
|
944
1023
|
timestamp: new Date(),
|
|
945
1024
|
}
|
|
946
|
-
handlers.forEach(h => h(event))
|
|
1025
|
+
handlers.forEach((h) => h(event))
|
|
947
1026
|
}
|
|
948
1027
|
}
|
|
949
1028
|
|
|
950
1029
|
function getAvailableAgents(): AgentInfo[] {
|
|
951
|
-
return Array.from(agents.values()).filter(a => {
|
|
1030
|
+
return Array.from(agents.values()).filter((a) => {
|
|
952
1031
|
const avail = availability.get(a.id)
|
|
953
1032
|
return avail?.status === 'available' || avail?.status === 'busy'
|
|
954
1033
|
})
|
|
@@ -1137,7 +1216,7 @@ export function createRoutingRuleEngine(
|
|
|
1137
1216
|
|
|
1138
1217
|
// Sort rules by priority (descending)
|
|
1139
1218
|
const sortedRules = [...rules]
|
|
1140
|
-
.filter(r => r.enabled !== false)
|
|
1219
|
+
.filter((r) => r.enabled !== false)
|
|
1141
1220
|
.sort((a, b) => b.priority - a.priority)
|
|
1142
1221
|
|
|
1143
1222
|
// Evaluate rules
|
|
@@ -1178,28 +1257,28 @@ export function createRoutingRuleEngine(
|
|
|
1178
1257
|
}
|
|
1179
1258
|
|
|
1180
1259
|
function removeRule(name: string): void {
|
|
1181
|
-
const index = rules.findIndex(r => r.name === name)
|
|
1260
|
+
const index = rules.findIndex((r) => r.name === name)
|
|
1182
1261
|
if (index !== -1) {
|
|
1183
1262
|
rules.splice(index, 1)
|
|
1184
1263
|
}
|
|
1185
1264
|
}
|
|
1186
1265
|
|
|
1187
1266
|
function updateRule(name: string, updates: Partial<RoutingRule>): void {
|
|
1188
|
-
const rule = rules.find(r => r.name === name)
|
|
1267
|
+
const rule = rules.find((r) => r.name === name)
|
|
1189
1268
|
if (rule) {
|
|
1190
1269
|
Object.assign(rule, updates)
|
|
1191
1270
|
}
|
|
1192
1271
|
}
|
|
1193
1272
|
|
|
1194
1273
|
function enableRule(name: string): void {
|
|
1195
|
-
const rule = rules.find(r => r.name === name)
|
|
1274
|
+
const rule = rules.find((r) => r.name === name)
|
|
1196
1275
|
if (rule) {
|
|
1197
1276
|
rule.enabled = true
|
|
1198
1277
|
}
|
|
1199
1278
|
}
|
|
1200
1279
|
|
|
1201
1280
|
function disableRule(name: string): void {
|
|
1202
|
-
const rule = rules.find(r => r.name === name)
|
|
1281
|
+
const rule = rules.find((r) => r.name === name)
|
|
1203
1282
|
if (rule) {
|
|
1204
1283
|
rule.enabled = false
|
|
1205
1284
|
}
|
|
@@ -1215,7 +1294,7 @@ export function createRoutingRuleEngine(
|
|
|
1215
1294
|
}
|
|
1216
1295
|
|
|
1217
1296
|
function removeAgent(agentId: string): void {
|
|
1218
|
-
agents = agents.filter(a => a.id !== agentId)
|
|
1297
|
+
agents = agents.filter((a) => a.id !== agentId)
|
|
1219
1298
|
defaultBalancer = undefined as any // Reset default balancer
|
|
1220
1299
|
}
|
|
1221
1300
|
|
|
@@ -1283,7 +1362,14 @@ export function createCompositeBalancer(
|
|
|
1283
1362
|
balancers.set(strategy, createRoundRobinBalancer(agents, balancerOptions))
|
|
1284
1363
|
}
|
|
1285
1364
|
}
|
|
1286
|
-
|
|
1365
|
+
const balancer = balancers.get(strategy)
|
|
1366
|
+
if (!balancer) {
|
|
1367
|
+
// Fallback to round-robin if strategy not found
|
|
1368
|
+
const fallback = createRoundRobinBalancer(agents, { metricsCollector: collector })
|
|
1369
|
+
balancers.set(strategy, fallback)
|
|
1370
|
+
return fallback
|
|
1371
|
+
}
|
|
1372
|
+
return balancer
|
|
1287
1373
|
}
|
|
1288
1374
|
|
|
1289
1375
|
function route(task: TaskRequest): RouteResult {
|
|
@@ -1293,7 +1379,7 @@ export function createCompositeBalancer(
|
|
|
1293
1379
|
let usedFallback = false
|
|
1294
1380
|
|
|
1295
1381
|
// Handle weighted strategies
|
|
1296
|
-
const weightedStrategies = config.strategies.map(s => {
|
|
1382
|
+
const weightedStrategies = config.strategies.map((s) => {
|
|
1297
1383
|
if (typeof s === 'string') {
|
|
1298
1384
|
return { strategy: s, weight: 1 }
|
|
1299
1385
|
}
|
|
@@ -1365,12 +1451,12 @@ export function createCompositeBalancer(
|
|
|
1365
1451
|
|
|
1366
1452
|
function addAgent(agent: AgentInfo): void {
|
|
1367
1453
|
agents.push(agent)
|
|
1368
|
-
balancers.forEach(b => b.addAgent(agent))
|
|
1454
|
+
balancers.forEach((b) => b.addAgent(agent))
|
|
1369
1455
|
}
|
|
1370
1456
|
|
|
1371
1457
|
function removeAgent(agentId: string): void {
|
|
1372
|
-
agents = agents.filter(a => a.id !== agentId)
|
|
1373
|
-
balancers.forEach(b => b.removeAgent(agentId))
|
|
1458
|
+
agents = agents.filter((a) => a.id !== agentId)
|
|
1459
|
+
balancers.forEach((b) => b.removeAgent(agentId))
|
|
1374
1460
|
}
|
|
1375
1461
|
|
|
1376
1462
|
function getAgents(): AgentInfo[] {
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger Interface for Digital Workers
|
|
3
|
+
*
|
|
4
|
+
* Provides a simple, extensible logging interface that can be injected
|
|
5
|
+
* into components for error logging and debugging. Consumers can provide
|
|
6
|
+
* their own logger implementation (e.g., winston, pino, console).
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Logger interface for structured logging
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* // Using with console
|
|
17
|
+
* const consoleLogger: Logger = {
|
|
18
|
+
* debug: (msg, meta) => console.debug(msg, meta),
|
|
19
|
+
* info: (msg, meta) => console.info(msg, meta),
|
|
20
|
+
* warn: (msg, meta) => console.warn(msg, meta),
|
|
21
|
+
* error: (msg, error, meta) => console.error(msg, error, meta),
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* // Using with a custom logger
|
|
25
|
+
* const customLogger: Logger = {
|
|
26
|
+
* debug: (msg, meta) => myLogger.debug({ message: msg, ...meta }),
|
|
27
|
+
* info: (msg, meta) => myLogger.info({ message: msg, ...meta }),
|
|
28
|
+
* warn: (msg, meta) => myLogger.warn({ message: msg, ...meta }),
|
|
29
|
+
* error: (msg, error, meta) => myLogger.error({ message: msg, error, ...meta }),
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export interface Logger {
|
|
34
|
+
/**
|
|
35
|
+
* Log debug-level messages
|
|
36
|
+
* @param msg - The log message
|
|
37
|
+
* @param meta - Optional metadata object
|
|
38
|
+
*/
|
|
39
|
+
debug(msg: string, meta?: object): void
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Log info-level messages
|
|
43
|
+
* @param msg - The log message
|
|
44
|
+
* @param meta - Optional metadata object
|
|
45
|
+
*/
|
|
46
|
+
info(msg: string, meta?: object): void
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Log warning-level messages
|
|
50
|
+
* @param msg - The log message
|
|
51
|
+
* @param meta - Optional metadata object
|
|
52
|
+
*/
|
|
53
|
+
warn(msg: string, meta?: object): void
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Log error-level messages
|
|
57
|
+
* @param msg - The log message
|
|
58
|
+
* @param error - Optional error object with stack trace
|
|
59
|
+
* @param meta - Optional metadata object
|
|
60
|
+
*/
|
|
61
|
+
error(msg: string, error?: Error, meta?: object): void
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A no-op logger that discards all log messages.
|
|
66
|
+
* Used as a default when no logger is provided.
|
|
67
|
+
*/
|
|
68
|
+
export const noopLogger: Logger = {
|
|
69
|
+
debug: () => {},
|
|
70
|
+
info: () => {},
|
|
71
|
+
warn: () => {},
|
|
72
|
+
error: () => {},
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Creates a console-based logger for debugging purposes.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* const transport = createSlackTransport({
|
|
81
|
+
* ...config,
|
|
82
|
+
* logger: createConsoleLogger(),
|
|
83
|
+
* })
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export function createConsoleLogger(): Logger {
|
|
87
|
+
return {
|
|
88
|
+
debug: (msg, meta) => console.debug(`[DEBUG] ${msg}`, meta ?? ''),
|
|
89
|
+
info: (msg, meta) => console.info(`[INFO] ${msg}`, meta ?? ''),
|
|
90
|
+
warn: (msg, meta) => console.warn(`[WARN] ${msg}`, meta ?? ''),
|
|
91
|
+
error: (msg, error, meta) => console.error(`[ERROR] ${msg}`, error ?? '', meta ?? ''),
|
|
92
|
+
}
|
|
93
|
+
}
|
package/src/notify.ts
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Notification functionality for digital workers
|
|
3
|
+
*
|
|
4
|
+
* IMPORTANT: Real Channel Delivery vs LLM Generation
|
|
5
|
+
* ---------------------------------------------------
|
|
6
|
+
* This module sends real notifications via communication channels,
|
|
7
|
+
* NOT LLM-generated notification content.
|
|
8
|
+
*
|
|
9
|
+
* - `digital-workers.notify()` - Sends actual notifications to Workers
|
|
10
|
+
* via real channels (Slack, email, SMS, etc.) with delivery tracking.
|
|
11
|
+
*
|
|
12
|
+
* - ai-functions does not have an equivalent `notify` primitive since
|
|
13
|
+
* it focuses on LLM operations, not communication channel delivery.
|
|
14
|
+
*
|
|
15
|
+
* Use digital-workers.notify() when you need:
|
|
16
|
+
* - Real notification delivery to people/agents
|
|
17
|
+
* - Multi-channel delivery (Slack + SMS for urgent)
|
|
18
|
+
* - Delivery tracking and confirmation
|
|
19
|
+
* - Priority-based channel selection
|
|
20
|
+
* - Scheduled notifications
|
|
21
|
+
*
|
|
22
|
+
* @module
|
|
3
23
|
*/
|
|
4
24
|
|
|
25
|
+
import { generateRequestId } from './utils/id.js'
|
|
5
26
|
import type {
|
|
6
27
|
Worker,
|
|
7
28
|
Team,
|
|
@@ -14,30 +35,39 @@ import type {
|
|
|
14
35
|
} from './types.js'
|
|
15
36
|
|
|
16
37
|
/**
|
|
17
|
-
* Send a notification to a
|
|
38
|
+
* Send a notification to a Worker (Human or AI Agent) via communication channels.
|
|
39
|
+
*
|
|
40
|
+
* **Unique to digital-workers:**
|
|
41
|
+
* This function sends real notifications via actual communication channels
|
|
42
|
+
* (Slack, email, SMS, phone, etc.). There is no equivalent in ai-functions
|
|
43
|
+
* since ai-functions focuses on LLM operations rather than external delivery.
|
|
18
44
|
*
|
|
19
|
-
*
|
|
20
|
-
* to the target's preferred channel if not specified.
|
|
45
|
+
* This is a **communication delivery primitive** for worker coordination.
|
|
21
46
|
*
|
|
22
|
-
* @param target - The worker or team to notify
|
|
47
|
+
* @param target - The worker or team to notify (routes to their configured channels)
|
|
23
48
|
* @param message - The notification message
|
|
24
|
-
* @param options - Notification options
|
|
25
|
-
* @returns Promise resolving to
|
|
49
|
+
* @param options - Notification options (channel, priority, metadata)
|
|
50
|
+
* @returns Promise resolving to NotifyResult with delivery status and metadata
|
|
26
51
|
*
|
|
27
52
|
* @example
|
|
28
53
|
* ```ts
|
|
29
54
|
* // Notify a worker via their preferred channel
|
|
30
|
-
* await notify(alice, 'Deployment completed successfully')
|
|
55
|
+
* const result = await notify(alice, 'Deployment completed successfully')
|
|
56
|
+
* console.log(result.sent) // true
|
|
57
|
+
* console.log(result.via) // ['slack']
|
|
31
58
|
*
|
|
32
59
|
* // Notify via specific channel
|
|
33
60
|
* await notify(alice, 'Urgent: Server down!', { via: 'slack' })
|
|
34
61
|
*
|
|
35
|
-
* // Notify via multiple channels
|
|
62
|
+
* // Notify via multiple channels (for urgent messages)
|
|
36
63
|
* await notify(alice, 'Critical alert', { via: ['slack', 'sms'] })
|
|
37
64
|
*
|
|
38
|
-
* // Notify a team
|
|
65
|
+
* // Notify a team (reaches all members via their channels)
|
|
39
66
|
* await notify(engineering, 'Sprint planning tomorrow', { via: 'slack' })
|
|
40
67
|
* ```
|
|
68
|
+
*
|
|
69
|
+
* @see {@link notify.alert} for high-priority urgent notifications
|
|
70
|
+
* @see {@link notify.schedule} for delayed/scheduled notifications
|
|
41
71
|
*/
|
|
42
72
|
export async function notify(
|
|
43
73
|
target: ActionTarget,
|
|
@@ -66,7 +96,10 @@ export async function notify(
|
|
|
66
96
|
const delivery = await Promise.all(
|
|
67
97
|
channels.map(async (channel) => {
|
|
68
98
|
try {
|
|
69
|
-
await sendToChannel(channel, message, contacts, {
|
|
99
|
+
await sendToChannel(channel, message, contacts, {
|
|
100
|
+
priority,
|
|
101
|
+
...(metadata !== undefined && { metadata }),
|
|
102
|
+
})
|
|
70
103
|
return { channel, status: 'sent' as const }
|
|
71
104
|
} catch (error) {
|
|
72
105
|
return {
|
|
@@ -286,18 +319,46 @@ async function sendToChannel(
|
|
|
286
319
|
throw new Error(`No ${channel} contact configured`)
|
|
287
320
|
}
|
|
288
321
|
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
322
|
+
// Import transport functions dynamically to avoid circular dependencies
|
|
323
|
+
const { channelToTransport, sendViaTransport, hasTransport, resolveAddress } = await import(
|
|
324
|
+
'./transports.js'
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
const transport = channelToTransport(channel)
|
|
328
|
+
const address = resolveAddress(contacts, channel)
|
|
329
|
+
|
|
330
|
+
// If transport is registered, use it for real delivery
|
|
331
|
+
if (hasTransport(transport) && address) {
|
|
332
|
+
const payload = {
|
|
333
|
+
to: address.value,
|
|
334
|
+
body: message,
|
|
335
|
+
type: 'notification' as const,
|
|
336
|
+
priority: (options.priority || 'normal') as 'low' | 'normal' | 'high' | 'urgent',
|
|
337
|
+
...(options.metadata !== undefined && { metadata: options.metadata }),
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const result = await sendViaTransport(transport, payload)
|
|
341
|
+
|
|
342
|
+
if (!result.success) {
|
|
343
|
+
throw new Error(`Failed to send notification via ${transport}: ${result.error}`)
|
|
344
|
+
}
|
|
345
|
+
return
|
|
346
|
+
}
|
|
293
347
|
|
|
294
|
-
//
|
|
295
|
-
|
|
348
|
+
// No transport registered - log to console for development/testing
|
|
349
|
+
// This provides visibility into what would be sent
|
|
350
|
+
console.log(`[digital-workers] Notification (${channel}):`, {
|
|
351
|
+
to: typeof contact === 'string' ? contact : JSON.stringify(contact),
|
|
352
|
+
message,
|
|
353
|
+
priority: options.priority || 'normal',
|
|
354
|
+
metadata: options.metadata,
|
|
355
|
+
note: `No transport registered for '${transport}'. Register a transport handler to enable real delivery.`,
|
|
356
|
+
})
|
|
296
357
|
}
|
|
297
358
|
|
|
298
359
|
/**
|
|
299
360
|
* Generate a unique message ID
|
|
300
361
|
*/
|
|
301
362
|
function generateMessageId(prefix = 'msg'): string {
|
|
302
|
-
return
|
|
363
|
+
return generateRequestId(prefix)
|
|
303
364
|
}
|