@absolutejs/voice 0.0.22-beta.1 → 0.0.22-beta.3

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/README.md CHANGED
@@ -73,6 +73,88 @@ const app = new Elysia()
73
73
 
74
74
  `createVoiceMemoryStore()` is dev-only. Real deployments should provide a shared store backed by Redis, Postgres, or equivalent.
75
75
 
76
+ ## Voice Assistants
77
+
78
+ Use `createVoiceAssistant(...)` when you want one product-level surface for a voice agent instead of wiring tools, guardrails, experiments, traces, and ops recipes separately. It returns a standard `onTurn` handler, plus an `ops` object you can pass to `voice(...)`.
79
+
80
+ ```ts
81
+ import {
82
+ createVoiceAssistant,
83
+ createVoiceExperiment,
84
+ createVoiceFileRuntimeStorage,
85
+ createVoiceMemoryStore,
86
+ createVoiceAgentTool,
87
+ voice
88
+ } from '@absolutejs/voice';
89
+ import { deepgram } from '@absolutejs/voice-deepgram';
90
+
91
+ const runtimeStorage = createVoiceFileRuntimeStorage({
92
+ directory: '.voice-runtime/support'
93
+ });
94
+
95
+ const lookupOrder = createVoiceAgentTool({
96
+ name: 'lookup_order',
97
+ description: 'Look up an order by id.',
98
+ execute: async ({ args }) => ({ orderId: args.orderId, status: 'shipped' })
99
+ });
100
+
101
+ const assistant = createVoiceAssistant({
102
+ id: 'support',
103
+ artifactPlan: {
104
+ ops: {
105
+ events: runtimeStorage.events,
106
+ reviews: runtimeStorage.reviews,
107
+ tasks: runtimeStorage.tasks
108
+ },
109
+ preset: {
110
+ name: 'support-triage',
111
+ options: {
112
+ queue: 'support-triage'
113
+ }
114
+ }
115
+ },
116
+ experiment: createVoiceExperiment({
117
+ id: 'support-prompt',
118
+ variants: [
119
+ { id: 'baseline', weight: 1 },
120
+ {
121
+ id: 'direct',
122
+ weight: 1,
123
+ system: 'You are concise, practical, and resolve the caller quickly.'
124
+ }
125
+ ]
126
+ }),
127
+ guardrails: {
128
+ beforeTurn: ({ turn }) =>
129
+ turn.text.toLowerCase().includes('human')
130
+ ? { escalate: { reason: 'caller requested a human' } }
131
+ : undefined
132
+ },
133
+ model: {
134
+ async generate({ messages, tools }) {
135
+ return {
136
+ assistantText: `I can help. Available tools: ${tools.map((tool) => tool.name).join(', ')}`
137
+ };
138
+ }
139
+ },
140
+ system: 'You are a support voice assistant.',
141
+ tools: [lookupOrder],
142
+ trace: runtimeStorage.traces
143
+ });
144
+
145
+ voice({
146
+ path: '/voice',
147
+ session: createVoiceMemoryStore(),
148
+ stt: deepgram({ apiKey: process.env.DEEPGRAM_API_KEY! }),
149
+ trace: runtimeStorage.traces,
150
+ ops: assistant.ops,
151
+ onTurn: assistant.onTurn,
152
+ onComplete: async () => {}
153
+ });
154
+ ```
155
+
156
+ Assistant experiments are deterministic by session id, so a caller stays on the same variant for a call. Variants can change the model, system prompt, tools, and tool-round budget; guardrails can block a turn before model execution or rewrite the returned `VoiceRouteResult`.
157
+
76
158
  ## Agent Tools And Squads
77
159
 
78
160
  For assistant-style products, use `createVoiceAgent(...)` as the `onTurn` handler. The agent layer is provider-neutral: plug in any model adapter, register server-side tools, and return normal voice route results like `assistantText`, `transfer`, `escalate`, or `complete`.
@@ -0,0 +1,123 @@
1
+ import { type VoiceAgent, type VoiceAgentModel, type VoiceAgentOptions, type VoiceAgentSquadOptions, type VoiceAgentTool } from './agent';
2
+ import { type VoiceOutcomeRecipeName, type VoiceOutcomeRecipeOptions } from './outcomeRecipes';
3
+ import type { VoiceNormalizedRouteConfig, VoiceOnTurnObjectHandler, VoiceRouteConfig, VoiceRouteResult, VoiceRuntimeOpsConfig, VoiceSessionRecord } from './types';
4
+ import type { StoredVoiceTraceEvent, VoiceTraceEventStore } from './trace';
5
+ export type VoiceAssistantPreset = VoiceOutcomeRecipeName;
6
+ export type VoiceAssistantArtifactPlan<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
7
+ ops?: VoiceRuntimeOpsConfig<TContext, TSession, TResult>;
8
+ preset?: VoiceAssistantPreset | {
9
+ name: VoiceAssistantPreset;
10
+ options?: VoiceOutcomeRecipeOptions;
11
+ };
12
+ };
13
+ export type VoiceAssistantGuardrailInput<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = Parameters<VoiceOnTurnObjectHandler<TContext, TSession, TResult>>[0] & {
14
+ assistantId: string;
15
+ };
16
+ export type VoiceAssistantOutputGuardrailInput<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = VoiceAssistantGuardrailInput<TContext, TSession, TResult> & {
17
+ result: VoiceRouteResult<TResult>;
18
+ };
19
+ export type VoiceAssistantGuardrails<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
20
+ beforeTurn?: (input: VoiceAssistantGuardrailInput<TContext, TSession, TResult>) => Promise<VoiceRouteResult<TResult> | void> | VoiceRouteResult<TResult> | void;
21
+ afterTurn?: (input: VoiceAssistantOutputGuardrailInput<TContext, TSession, TResult>) => Promise<VoiceRouteResult<TResult> | void> | VoiceRouteResult<TResult> | void;
22
+ };
23
+ export type VoiceAssistantVariant<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
24
+ id: string;
25
+ maxToolRounds?: number;
26
+ metadata?: Record<string, unknown>;
27
+ model?: VoiceAgentModel<TContext, TSession, TResult>;
28
+ system?: VoiceAgentOptions<TContext, TSession, TResult>['system'];
29
+ tools?: Array<VoiceAgentTool<TContext, TSession, Record<string, unknown>, unknown, TResult>>;
30
+ weight?: number;
31
+ };
32
+ export type VoiceAssistantExperimentResolverInput<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord> = {
33
+ assistantId: string;
34
+ context: TContext;
35
+ session: TSession;
36
+ turnId?: string;
37
+ };
38
+ export type VoiceAssistantExperiment<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
39
+ id: string;
40
+ resolve: (input: VoiceAssistantExperimentResolverInput<TContext, TSession>) => VoiceAssistantVariant<TContext, TSession, TResult>;
41
+ variants: Array<VoiceAssistantVariant<TContext, TSession, TResult>>;
42
+ };
43
+ export type VoiceAssistantExperimentOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
44
+ id: string;
45
+ selectVariant?: (input: VoiceAssistantExperimentResolverInput<TContext, TSession> & {
46
+ variants: Array<VoiceAssistantVariant<TContext, TSession, TResult>>;
47
+ }) => VoiceAssistantVariant<TContext, TSession, TResult> | string | void;
48
+ variants: Array<VoiceAssistantVariant<TContext, TSession, TResult>>;
49
+ };
50
+ type VoiceAssistantAgentSource<TContext, TSession extends VoiceSessionRecord, TResult> = {
51
+ agent: VoiceAgent<TContext, TSession, TResult>;
52
+ agents?: never;
53
+ defaultAgentId?: never;
54
+ maxHandoffsPerTurn?: never;
55
+ maxToolRounds?: never;
56
+ model?: never;
57
+ selectAgent?: never;
58
+ system?: never;
59
+ tools?: never;
60
+ } | {
61
+ agent?: never;
62
+ agents: Array<VoiceAgent<TContext, TSession, TResult>>;
63
+ defaultAgentId: string;
64
+ maxHandoffsPerTurn?: number;
65
+ maxToolRounds?: never;
66
+ model?: never;
67
+ selectAgent?: VoiceAgentSquadOptions<TContext, TSession, TResult>['selectAgent'];
68
+ system?: never;
69
+ tools?: never;
70
+ } | {
71
+ agent?: never;
72
+ agents?: never;
73
+ defaultAgentId?: never;
74
+ maxHandoffsPerTurn?: never;
75
+ maxToolRounds?: number;
76
+ model: VoiceAgentModel<TContext, TSession, TResult>;
77
+ selectAgent?: never;
78
+ system?: VoiceAgentOptions<TContext, TSession, TResult>['system'];
79
+ tools?: Array<VoiceAgentTool<TContext, TSession, Record<string, unknown>, unknown, TResult>>;
80
+ };
81
+ export type VoiceAssistantOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = VoiceAssistantAgentSource<TContext, TSession, TResult> & {
82
+ artifactPlan?: VoiceAssistantArtifactPlan<TContext, TSession, TResult>;
83
+ experiment?: VoiceAssistantExperiment<TContext, TSession, TResult>;
84
+ guardrails?: VoiceAssistantGuardrails<TContext, TSession, TResult>;
85
+ id: string;
86
+ ops?: VoiceRuntimeOpsConfig<TContext, TSession, TResult>;
87
+ trace?: VoiceAgentOptions<TContext, TSession, TResult>['trace'];
88
+ };
89
+ export type VoiceAssistant<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
90
+ agent: VoiceAgent<TContext, TSession, TResult>;
91
+ id: string;
92
+ onTurn: VoiceOnTurnObjectHandler<TContext, TSession, TResult>;
93
+ ops?: VoiceRuntimeOpsConfig<TContext, TSession, TResult>;
94
+ route: (overrides: Omit<VoiceRouteConfig<TContext, TSession, TResult>, 'onComplete' | 'onTurn'> & {
95
+ onComplete?: VoiceRouteConfig<TContext, TSession, TResult>['onComplete'];
96
+ }) => VoiceNormalizedRouteConfig<TContext, TSession, TResult>;
97
+ };
98
+ export type VoiceAssistantRunSummary = {
99
+ assistantId: string;
100
+ artifactPlans: Record<string, number>;
101
+ averageElapsedMs?: number;
102
+ blockedGuardrailCount: number;
103
+ escalationCount: number;
104
+ experiments: Record<string, number>;
105
+ guardrailCount: number;
106
+ outcomes: Record<string, number>;
107
+ runCount: number;
108
+ sessions: number;
109
+ toolCalls: Record<string, number>;
110
+ transferCount: number;
111
+ variants: Record<string, number>;
112
+ };
113
+ export type VoiceAssistantRunsSummary = {
114
+ assistants: VoiceAssistantRunSummary[];
115
+ totalRuns: number;
116
+ };
117
+ export declare const createVoiceExperiment: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(options: VoiceAssistantExperimentOptions<TContext, TSession, TResult>) => VoiceAssistantExperiment<TContext, TSession, TResult>;
118
+ export declare const createVoiceAssistant: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(options: VoiceAssistantOptions<TContext, TSession, TResult>) => VoiceAssistant<TContext, TSession, TResult>;
119
+ export declare const summarizeVoiceAssistantRuns: (input: StoredVoiceTraceEvent[] | {
120
+ events?: StoredVoiceTraceEvent[];
121
+ store?: VoiceTraceEventStore;
122
+ }) => Promise<VoiceAssistantRunsSummary>;
123
+ export {};
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { voice } from './plugin';
2
+ export { createVoiceAssistant, createVoiceExperiment, summarizeVoiceAssistantRuns } from './assistant';
2
3
  export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool } from './agent';
3
4
  export { createStoredVoiceCallReviewArtifact, createStoredVoiceExternalObjectMap, createStoredVoiceIntegrationEvent, createStoredVoiceOpsTask, createVoiceFileExternalObjectMapStore, createVoiceFileIntegrationEventStore, createVoiceFileReviewStore, createVoiceFileRuntimeStorage, createVoiceFileSessionStore, createVoiceFileTaskStore, createVoiceFileTraceSinkDeliveryStore, createVoiceFileTraceEventStore } from './fileStore';
4
5
  export { buildVoiceTraceReplay, createVoiceMemoryTraceSinkDeliveryStore, createVoiceTraceHTTPSink, createVoiceMemoryTraceEventStore, createVoiceTraceSinkDeliveryId, createVoiceTraceSinkDeliveryRecord, createVoiceTraceSinkStore, createVoiceTraceEvent, createVoiceTraceEventId, deliverVoiceTraceEventsToSinks, evaluateVoiceTrace, exportVoiceTrace, filterVoiceTraceEvents, pruneVoiceTraceEvents, redactVoiceTraceEvent, redactVoiceTraceEvents, redactVoiceTraceText, renderVoiceTraceHTML, renderVoiceTraceMarkdown, resolveVoiceTraceRedactionOptions, selectVoiceTraceEventsForPrune, summarizeVoiceTrace } from './trace';
@@ -21,6 +22,7 @@ export { conditionAudioChunk, resolveAudioConditioningConfig } from './audioCond
21
22
  export { resolveVoiceRuntimePreset } from './presets';
22
23
  export { resolveTurnDetectionConfig, TURN_PROFILE_DEFAULTS } from './turnProfiles';
23
24
  export { createVoiceCallReviewFromLiveTelephonyReport, createVoiceCallReviewRecorder, renderVoiceCallReviewHTML, renderVoiceCallReviewMarkdown } from './testing/review';
25
+ export type { VoiceAssistant, VoiceAssistantArtifactPlan, VoiceAssistantExperiment, VoiceAssistantExperimentOptions, VoiceAssistantGuardrailInput, VoiceAssistantGuardrails, VoiceAssistantOptions, VoiceAssistantOutputGuardrailInput, VoiceAssistantPreset, VoiceAssistantRunsSummary, VoiceAssistantRunSummary, VoiceAssistantVariant } from './assistant';
24
26
  export type { VoiceAgent, VoiceAgentMessage, VoiceAgentMessageRole, VoiceAgentModel, VoiceAgentModelInput, VoiceAgentModelOutput, VoiceAgentOptions, VoiceAgentRunResult, VoiceAgentSquadOptions, VoiceAgentTool, VoiceAgentToolCall, VoiceAgentToolResult } from './agent';
25
27
  export type { VoiceOpsRuntime, VoiceOpsRuntimeConfig, VoiceOpsRuntimeSummary, VoiceOpsRuntimeSinkWorkerConfig, VoiceOpsRuntimeTaskWorkerConfig, VoiceOpsRuntimeTickResult, VoiceOpsRuntimeWebhookWorkerConfig } from './opsRuntime';
26
28
  export type { VoiceOpsPresetName, VoiceOpsPresetOverrides, VoiceResolvedOpsPreset } from './opsPresets';
package/dist/index.js CHANGED
@@ -5368,6 +5368,567 @@ var createVoiceAgentSquad = (options) => {
5368
5368
  run
5369
5369
  };
5370
5370
  };
5371
+
5372
+ // src/outcomeRecipes.ts
5373
+ var RECIPE_DEFAULTS = {
5374
+ "appointment-booking": {
5375
+ completedAction: "Verify appointment details, confirm calendar state, and send any required confirmation.",
5376
+ completedDescription: "The call completed an appointment-booking flow and should be checked against the scheduling system.",
5377
+ completedKind: "appointment-booking",
5378
+ completedTitle: "Confirm booked appointment",
5379
+ defaultCompletedCreatesTask: true,
5380
+ defaultDueInMs: 30 * 60000,
5381
+ defaultPriority: "normal",
5382
+ defaultQueue: "appointments",
5383
+ description: "Creates appointment confirmation work for completed calls and callback/retry work for missed booking attempts.",
5384
+ escalationQueue: "appointments-escalations"
5385
+ },
5386
+ "lead-qualification": {
5387
+ completedAction: "Review qualification signals, update CRM fields, and route the lead to the right owner.",
5388
+ completedDescription: "The call completed a lead-qualification flow and should be reviewed for sales follow-up.",
5389
+ completedKind: "lead-qualification",
5390
+ completedTitle: "Review qualified lead",
5391
+ defaultCompletedCreatesTask: true,
5392
+ defaultDueInMs: 15 * 60000,
5393
+ defaultPriority: "high",
5394
+ defaultQueue: "sales-leads",
5395
+ description: "Creates sales follow-up work for completed qualification calls and fast callbacks for missed leads.",
5396
+ escalationQueue: "sales-escalations"
5397
+ },
5398
+ "support-triage": {
5399
+ completedAction: "Review the triage result, confirm the support category, and route any unresolved issue.",
5400
+ completedDescription: "The call completed support triage and may need queue routing or human follow-up.",
5401
+ completedKind: "support-triage",
5402
+ completedTitle: "Review support triage",
5403
+ defaultCompletedCreatesTask: true,
5404
+ defaultDueInMs: 20 * 60000,
5405
+ defaultPriority: "normal",
5406
+ defaultQueue: "support-triage",
5407
+ description: "Creates support triage work for completed calls and urgent escalation/callback work for unresolved callers.",
5408
+ escalationQueue: "support-escalations"
5409
+ },
5410
+ "voicemail-callback": {
5411
+ completedAction: "No callback is required for completed calls.",
5412
+ completedDescription: "The call completed without requiring voicemail follow-up.",
5413
+ completedKind: "callback",
5414
+ completedTitle: "Completed call",
5415
+ defaultCompletedCreatesTask: false,
5416
+ defaultDueInMs: 15 * 60000,
5417
+ defaultPriority: "high",
5418
+ defaultQueue: "callbacks",
5419
+ description: "Creates callback work for voicemail, no-answer, failed, or escalated calls while ignoring completed calls.",
5420
+ escalationQueue: "callback-escalations"
5421
+ },
5422
+ "warm-transfer": {
5423
+ completedAction: "Confirm the handoff target received the caller context and close the transfer loop.",
5424
+ completedDescription: "The call is part of a warm-transfer flow and should be verified downstream.",
5425
+ completedKind: "transfer-check",
5426
+ completedTitle: "Verify warm transfer",
5427
+ defaultCompletedCreatesTask: false,
5428
+ defaultDueInMs: 10 * 60000,
5429
+ defaultPriority: "normal",
5430
+ defaultQueue: "transfer-verification",
5431
+ description: "Creates transfer verification work for transferred calls and escalation work when the handoff fails.",
5432
+ escalationQueue: "transfer-escalations"
5433
+ }
5434
+ };
5435
+ var buildRecipeTask = (input) => {
5436
+ const createdAt = input.review.generatedAt ?? Date.now();
5437
+ const queue = input.options.queue ?? input.defaults.defaultQueue;
5438
+ const target = input.options.target ?? input.review.postCall?.target;
5439
+ const common = {
5440
+ assignee: input.options.assignee,
5441
+ createdAt,
5442
+ history: [
5443
+ {
5444
+ actor: "system",
5445
+ at: createdAt,
5446
+ detail: input.review.postCall?.summary,
5447
+ type: "created"
5448
+ }
5449
+ ],
5450
+ id: `${input.review.id}:${input.defaults.completedKind}`,
5451
+ intakeId: input.review.id,
5452
+ outcome: input.review.summary.outcome,
5453
+ priority: input.options.priority ?? input.defaults.defaultPriority,
5454
+ queue,
5455
+ reviewId: input.review.id,
5456
+ status: "open",
5457
+ target,
5458
+ updatedAt: createdAt
5459
+ };
5460
+ switch (input.disposition) {
5461
+ case "completed":
5462
+ if (!(input.options.completedCreatesTask ?? input.defaults.defaultCompletedCreatesTask)) {
5463
+ return null;
5464
+ }
5465
+ return {
5466
+ ...common,
5467
+ description: input.defaults.completedDescription,
5468
+ kind: input.defaults.completedKind,
5469
+ recommendedAction: input.defaults.completedAction,
5470
+ title: target ? `${input.defaults.completedTitle}: ${target}` : input.defaults.completedTitle
5471
+ };
5472
+ case "voicemail":
5473
+ return {
5474
+ ...common,
5475
+ description: input.review.postCall?.summary ?? "The caller reached voicemail and needs a callback.",
5476
+ id: `${input.review.id}:callback`,
5477
+ kind: "callback",
5478
+ recommendedAction: input.review.postCall?.recommendedAction ?? "Call the customer back and continue the original flow.",
5479
+ title: target ? `Call back ${target}` : "Call back voicemail lead"
5480
+ };
5481
+ case "no-answer":
5482
+ return {
5483
+ ...common,
5484
+ description: input.review.postCall?.summary ?? "The call did not reach a live respondent and should be retried.",
5485
+ id: `${input.review.id}:retry`,
5486
+ kind: "callback",
5487
+ recommendedAction: input.review.postCall?.recommendedAction ?? "Retry the call or schedule a callback.",
5488
+ title: "Retry no-answer call"
5489
+ };
5490
+ case "transferred":
5491
+ return {
5492
+ ...common,
5493
+ description: input.review.postCall?.summary ?? "The call was transferred and should be verified downstream.",
5494
+ id: `${input.review.id}:transfer-check`,
5495
+ kind: "transfer-check",
5496
+ recommendedAction: input.review.postCall?.recommendedAction ?? "Confirm the receiving team got the caller context.",
5497
+ title: target ? `Verify transfer to ${target}` : "Verify call transfer"
5498
+ };
5499
+ case "escalated":
5500
+ return {
5501
+ ...common,
5502
+ description: input.review.postCall?.summary ?? "The call escalated and needs human review.",
5503
+ id: `${input.review.id}:escalation`,
5504
+ kind: "escalation",
5505
+ priority: "urgent",
5506
+ queue: input.options.escalationQueue ?? input.defaults.escalationQueue,
5507
+ assignee: input.options.escalationAssignee ?? input.options.assignee,
5508
+ recommendedAction: input.review.postCall?.recommendedAction ?? "Review the escalated call and respond immediately.",
5509
+ title: "Review escalated call"
5510
+ };
5511
+ case "failed":
5512
+ case "closed":
5513
+ return {
5514
+ ...common,
5515
+ description: input.review.postCall?.summary ?? "The call ended before successful completion and needs review.",
5516
+ id: `${input.review.id}:retry-review`,
5517
+ kind: "retry-review",
5518
+ priority: "high",
5519
+ recommendedAction: input.review.postCall?.recommendedAction ?? "Inspect the call and decide whether to retry, escalate, or close.",
5520
+ title: "Inspect incomplete call"
5521
+ };
5522
+ default:
5523
+ return null;
5524
+ }
5525
+ };
5526
+ var resolveVoiceOutcomeRecipe = (name, options = {}) => {
5527
+ const defaults = RECIPE_DEFAULTS[name];
5528
+ const taskPolicies = {
5529
+ completed: {
5530
+ assignee: options.assignee,
5531
+ dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
5532
+ name: `${name}-completed`,
5533
+ priority: options.priority ?? defaults.defaultPriority,
5534
+ queue: options.queue ?? defaults.defaultQueue
5535
+ },
5536
+ escalated: {
5537
+ assignee: options.escalationAssignee ?? options.assignee,
5538
+ dueInMs: Math.min(options.dueInMs ?? defaults.defaultDueInMs, 10 * 60000),
5539
+ name: `${name}-escalation`,
5540
+ priority: "urgent",
5541
+ queue: options.escalationQueue ?? defaults.escalationQueue
5542
+ },
5543
+ failed: {
5544
+ assignee: options.assignee,
5545
+ dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
5546
+ name: `${name}-failed-review`,
5547
+ priority: "high",
5548
+ queue: options.queue ?? defaults.defaultQueue
5549
+ },
5550
+ "no-answer": {
5551
+ assignee: options.assignee,
5552
+ dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
5553
+ name: `${name}-no-answer`,
5554
+ priority: options.priority ?? defaults.defaultPriority,
5555
+ queue: options.queue ?? defaults.defaultQueue
5556
+ },
5557
+ transferred: {
5558
+ assignee: options.assignee,
5559
+ dueInMs: Math.min(options.dueInMs ?? defaults.defaultDueInMs, 20 * 60000),
5560
+ name: `${name}-transfer-check`,
5561
+ priority: options.priority ?? defaults.defaultPriority,
5562
+ queue: name === "warm-transfer" ? options.queue ?? defaults.defaultQueue : "transfer-verification"
5563
+ },
5564
+ voicemail: {
5565
+ assignee: options.assignee,
5566
+ dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
5567
+ name: `${name}-voicemail`,
5568
+ priority: options.priority ?? defaults.defaultPriority,
5569
+ queue: options.queue ?? defaults.defaultQueue
5570
+ }
5571
+ };
5572
+ const taskAssignmentRules = [
5573
+ {
5574
+ assign: options.escalationAssignee ?? options.assignee,
5575
+ description: `Route urgent ${name} work to the escalation lane.`,
5576
+ name: `${name}-urgent-routing`,
5577
+ queue: options.escalationQueue ?? defaults.escalationQueue,
5578
+ when: {
5579
+ priority: "urgent"
5580
+ }
5581
+ }
5582
+ ].filter((rule) => rule.assign || rule.queue);
5583
+ return {
5584
+ createTaskFromReview: ({ disposition, review }) => buildRecipeTask({
5585
+ defaults,
5586
+ disposition,
5587
+ options,
5588
+ review
5589
+ }),
5590
+ description: defaults.description,
5591
+ name,
5592
+ taskAssignmentRules,
5593
+ taskPolicies
5594
+ };
5595
+ };
5596
+
5597
+ // src/assistant.ts
5598
+ var hashString = (value) => {
5599
+ let hash = 2166136261;
5600
+ for (let index = 0;index < value.length; index += 1) {
5601
+ hash ^= value.charCodeAt(index);
5602
+ hash = Math.imul(hash, 16777619);
5603
+ }
5604
+ return hash >>> 0;
5605
+ };
5606
+ var increment = (record, key) => {
5607
+ record[key] = (record[key] ?? 0) + 1;
5608
+ };
5609
+ var resolveOutcome = (result) => {
5610
+ if (result.transfer) {
5611
+ return "transferred";
5612
+ }
5613
+ if (result.escalate) {
5614
+ return "escalated";
5615
+ }
5616
+ if (result.voicemail) {
5617
+ return "voicemail";
5618
+ }
5619
+ if (result.noAnswer) {
5620
+ return "no-answer";
5621
+ }
5622
+ if (result.complete) {
5623
+ return "completed";
5624
+ }
5625
+ return "continued";
5626
+ };
5627
+ var resolveArtifactPlanName = (artifactPlan) => {
5628
+ const preset = artifactPlan?.preset;
5629
+ if (!preset) {
5630
+ return artifactPlan?.ops ? "custom" : undefined;
5631
+ }
5632
+ return typeof preset === "string" ? preset : preset.name;
5633
+ };
5634
+ var appendAssistantTrace = async (input) => {
5635
+ await input.trace?.append({
5636
+ at: Date.now(),
5637
+ payload: {
5638
+ assistantId: input.assistantId,
5639
+ ...input.event
5640
+ },
5641
+ scenarioId: input.session.scenarioId,
5642
+ sessionId: input.session.id,
5643
+ turnId: input.turnId,
5644
+ type: input.type
5645
+ });
5646
+ };
5647
+ var resolvePresetOps = (artifactPlan) => {
5648
+ const preset = artifactPlan?.preset;
5649
+ if (!preset) {
5650
+ return artifactPlan?.ops;
5651
+ }
5652
+ const recipe = typeof preset === "string" ? resolveVoiceOutcomeRecipe(preset) : resolveVoiceOutcomeRecipe(preset.name, preset.options);
5653
+ return {
5654
+ ...recipe,
5655
+ ...artifactPlan?.ops
5656
+ };
5657
+ };
5658
+ var mergeOps = (base, override) => {
5659
+ if (!base && !override) {
5660
+ return;
5661
+ }
5662
+ return {
5663
+ ...base,
5664
+ ...override,
5665
+ taskAssignmentRules: base?.taskAssignmentRules || override?.taskAssignmentRules ? [
5666
+ ...base?.taskAssignmentRules ?? [],
5667
+ ...override?.taskAssignmentRules ?? []
5668
+ ] : undefined,
5669
+ taskPolicies: base?.taskPolicies || override?.taskPolicies ? {
5670
+ ...base?.taskPolicies ?? {},
5671
+ ...override?.taskPolicies ?? {}
5672
+ } : undefined
5673
+ };
5674
+ };
5675
+ var createVoiceExperiment = (options) => {
5676
+ if (!options.variants.length) {
5677
+ throw new Error("createVoiceExperiment requires at least one variant.");
5678
+ }
5679
+ const firstVariant = options.variants[0];
5680
+ return {
5681
+ id: options.id,
5682
+ resolve: (input) => {
5683
+ const selected = options.selectVariant?.({
5684
+ ...input,
5685
+ variants: options.variants
5686
+ });
5687
+ if (selected && typeof selected !== "object") {
5688
+ const variant = options.variants.find((item) => item.id === selected);
5689
+ if (variant) {
5690
+ return variant;
5691
+ }
5692
+ }
5693
+ if (selected && typeof selected === "object" && "id" in selected) {
5694
+ return selected;
5695
+ }
5696
+ const totalWeight = options.variants.reduce((total, variant) => total + Math.max(0, variant.weight ?? 1), 0);
5697
+ if (totalWeight <= 0) {
5698
+ return firstVariant;
5699
+ }
5700
+ const bucket = hashString(`${options.id}:${input.assistantId}:${input.session.id}`) % totalWeight;
5701
+ let cursor = 0;
5702
+ for (const variant of options.variants) {
5703
+ cursor += Math.max(0, variant.weight ?? 1);
5704
+ if (bucket < cursor) {
5705
+ return variant;
5706
+ }
5707
+ }
5708
+ return firstVariant;
5709
+ },
5710
+ variants: options.variants
5711
+ };
5712
+ };
5713
+ var createVoiceAssistant = (options) => {
5714
+ const ops = mergeOps(resolvePresetOps(options.artifactPlan), options.ops);
5715
+ const artifactPlanName = resolveArtifactPlanName(options.artifactPlan);
5716
+ let agent;
5717
+ const baseModelOptions = "model" in options && options.model ? {
5718
+ maxToolRounds: options.maxToolRounds,
5719
+ model: options.model,
5720
+ system: options.system,
5721
+ tools: options.tools
5722
+ } : undefined;
5723
+ if ("agent" in options && options.agent) {
5724
+ agent = options.agent;
5725
+ } else if ("agents" in options && options.agents) {
5726
+ agent = createVoiceAgentSquad({
5727
+ agents: options.agents,
5728
+ defaultAgentId: options.defaultAgentId,
5729
+ id: options.id,
5730
+ maxHandoffsPerTurn: options.maxHandoffsPerTurn,
5731
+ selectAgent: options.selectAgent,
5732
+ trace: options.trace
5733
+ });
5734
+ } else {
5735
+ agent = createVoiceAgent({
5736
+ id: options.id,
5737
+ maxToolRounds: options.maxToolRounds,
5738
+ model: options.model,
5739
+ system: options.system,
5740
+ trace: options.trace,
5741
+ tools: options.tools
5742
+ });
5743
+ }
5744
+ const onTurn = async (input) => {
5745
+ const guardrailInput = {
5746
+ ...input,
5747
+ assistantId: options.id
5748
+ };
5749
+ const blocked = await options.guardrails?.beforeTurn?.(guardrailInput);
5750
+ if (blocked) {
5751
+ await appendAssistantTrace({
5752
+ assistantId: options.id,
5753
+ event: {
5754
+ action: "blocked",
5755
+ artifactPlan: artifactPlanName,
5756
+ outcome: resolveOutcome(blocked)
5757
+ },
5758
+ session: input.session,
5759
+ trace: options.trace,
5760
+ turnId: input.turn.id,
5761
+ type: "assistant.guardrail"
5762
+ });
5763
+ await appendAssistantTrace({
5764
+ assistantId: options.id,
5765
+ event: {
5766
+ artifactPlan: artifactPlanName,
5767
+ blocked: true,
5768
+ experimentId: options.experiment?.id,
5769
+ outcome: resolveOutcome(blocked)
5770
+ },
5771
+ session: input.session,
5772
+ trace: options.trace,
5773
+ turnId: input.turn.id,
5774
+ type: "assistant.run"
5775
+ });
5776
+ return blocked;
5777
+ }
5778
+ const startedAt = Date.now();
5779
+ const variant = options.experiment?.resolve({
5780
+ assistantId: options.id,
5781
+ context: input.context,
5782
+ session: input.session,
5783
+ turnId: input.turn.id
5784
+ });
5785
+ const runner = variant && baseModelOptions ? createVoiceAgent({
5786
+ id: `${options.id}:${variant.id}`,
5787
+ maxToolRounds: variant.maxToolRounds ?? baseModelOptions.maxToolRounds,
5788
+ model: variant.model ?? baseModelOptions.model,
5789
+ system: variant.system ?? baseModelOptions.system,
5790
+ trace: options.trace,
5791
+ tools: variant.tools ?? baseModelOptions.tools
5792
+ }) : agent;
5793
+ const runResult = await runner.run(input) ?? {};
5794
+ const result = runResult;
5795
+ const guarded = await options.guardrails?.afterTurn?.({
5796
+ ...guardrailInput,
5797
+ result
5798
+ });
5799
+ const finalResult = guarded ?? result;
5800
+ if (guarded) {
5801
+ await appendAssistantTrace({
5802
+ assistantId: options.id,
5803
+ event: {
5804
+ action: "rewritten",
5805
+ artifactPlan: artifactPlanName,
5806
+ experimentId: options.experiment?.id,
5807
+ outcome: resolveOutcome(finalResult),
5808
+ variantId: variant?.id
5809
+ },
5810
+ session: input.session,
5811
+ trace: options.trace,
5812
+ turnId: input.turn.id,
5813
+ type: "assistant.guardrail"
5814
+ });
5815
+ }
5816
+ await appendAssistantTrace({
5817
+ assistantId: options.id,
5818
+ event: {
5819
+ artifactPlan: artifactPlanName,
5820
+ blocked: false,
5821
+ elapsedMs: Date.now() - startedAt,
5822
+ escalated: Boolean(finalResult.escalate),
5823
+ experimentId: options.experiment?.id,
5824
+ outcome: resolveOutcome(finalResult),
5825
+ toolNames: result.toolResults?.map((tool) => tool.toolName) ?? [],
5826
+ transferred: Boolean(finalResult.transfer),
5827
+ variantId: variant?.id
5828
+ },
5829
+ session: input.session,
5830
+ trace: options.trace,
5831
+ turnId: input.turn.id,
5832
+ type: "assistant.run"
5833
+ });
5834
+ return finalResult;
5835
+ };
5836
+ return {
5837
+ agent,
5838
+ id: options.id,
5839
+ onTurn,
5840
+ ops,
5841
+ route: (overrides) => ({
5842
+ ...overrides,
5843
+ onComplete: overrides.onComplete ?? (() => {
5844
+ return;
5845
+ }),
5846
+ onTurn
5847
+ })
5848
+ };
5849
+ };
5850
+ var summarizeVoiceAssistantRuns = async (input) => {
5851
+ const events = Array.isArray(input) ? input : input.events ?? await input.store?.list() ?? [];
5852
+ const assistantRuns = events.filter((event) => event.type === "assistant.run");
5853
+ const guardrails = events.filter((event) => event.type === "assistant.guardrail");
5854
+ const byAssistant = new Map;
5855
+ const getSummary = (assistantId) => {
5856
+ let summary = byAssistant.get(assistantId);
5857
+ if (!summary) {
5858
+ summary = {
5859
+ assistantId,
5860
+ artifactPlans: {},
5861
+ blockedGuardrailCount: 0,
5862
+ elapsedCount: 0,
5863
+ elapsedTotal: 0,
5864
+ escalationCount: 0,
5865
+ experiments: {},
5866
+ guardrailCount: 0,
5867
+ outcomes: {},
5868
+ runCount: 0,
5869
+ sessionIds: new Set,
5870
+ sessions: 0,
5871
+ toolCalls: {},
5872
+ transferCount: 0,
5873
+ variants: {}
5874
+ };
5875
+ byAssistant.set(assistantId, summary);
5876
+ }
5877
+ return summary;
5878
+ };
5879
+ for (const event of assistantRuns) {
5880
+ const assistantId = typeof event.payload.assistantId === "string" ? event.payload.assistantId : "unknown";
5881
+ const summary = getSummary(assistantId);
5882
+ summary.runCount += 1;
5883
+ summary.sessionIds.add(event.sessionId);
5884
+ if (typeof event.payload.artifactPlan === "string") {
5885
+ increment(summary.artifactPlans, event.payload.artifactPlan);
5886
+ }
5887
+ if (typeof event.payload.experimentId === "string") {
5888
+ increment(summary.experiments, event.payload.experimentId);
5889
+ }
5890
+ if (typeof event.payload.variantId === "string") {
5891
+ increment(summary.variants, event.payload.variantId);
5892
+ }
5893
+ if (typeof event.payload.outcome === "string") {
5894
+ increment(summary.outcomes, event.payload.outcome);
5895
+ }
5896
+ if (event.payload.escalated === true) {
5897
+ summary.escalationCount += 1;
5898
+ }
5899
+ if (event.payload.transferred === true) {
5900
+ summary.transferCount += 1;
5901
+ }
5902
+ if (event.payload.blocked === true) {
5903
+ summary.blockedGuardrailCount += 1;
5904
+ }
5905
+ if (typeof event.payload.elapsedMs === "number") {
5906
+ summary.elapsedCount += 1;
5907
+ summary.elapsedTotal += event.payload.elapsedMs;
5908
+ }
5909
+ if (Array.isArray(event.payload.toolNames)) {
5910
+ for (const toolName of event.payload.toolNames) {
5911
+ if (typeof toolName === "string") {
5912
+ increment(summary.toolCalls, toolName);
5913
+ }
5914
+ }
5915
+ }
5916
+ }
5917
+ for (const event of guardrails) {
5918
+ const assistantId = typeof event.payload.assistantId === "string" ? event.payload.assistantId : "unknown";
5919
+ const summary = getSummary(assistantId);
5920
+ summary.guardrailCount += 1;
5921
+ }
5922
+ const assistants = [...byAssistant.values()].map(({ elapsedCount, elapsedTotal, sessionIds, ...summary }) => ({
5923
+ ...summary,
5924
+ averageElapsedMs: elapsedCount > 0 ? Math.round(elapsedTotal / elapsedCount) : undefined,
5925
+ sessions: sessionIds.size
5926
+ }));
5927
+ return {
5928
+ assistants: assistants.sort((left, right) => left.assistantId.localeCompare(right.assistantId)),
5929
+ totalRuns: assistantRuns.length
5930
+ };
5931
+ };
5371
5932
  // src/fileStore.ts
5372
5933
  import { mkdir, readFile, readdir, rename, rm, writeFile } from "fs/promises";
5373
5934
  import { join } from "path";
@@ -7987,230 +8548,6 @@ var resolveVoiceOpsPreset = (name, overrides = {}) => {
7987
8548
  taskPolicies: mergePolicies(preset.taskPolicies, overrides.taskPolicies)
7988
8549
  };
7989
8550
  };
7990
- // src/outcomeRecipes.ts
7991
- var RECIPE_DEFAULTS = {
7992
- "appointment-booking": {
7993
- completedAction: "Verify appointment details, confirm calendar state, and send any required confirmation.",
7994
- completedDescription: "The call completed an appointment-booking flow and should be checked against the scheduling system.",
7995
- completedKind: "appointment-booking",
7996
- completedTitle: "Confirm booked appointment",
7997
- defaultCompletedCreatesTask: true,
7998
- defaultDueInMs: 30 * 60000,
7999
- defaultPriority: "normal",
8000
- defaultQueue: "appointments",
8001
- description: "Creates appointment confirmation work for completed calls and callback/retry work for missed booking attempts.",
8002
- escalationQueue: "appointments-escalations"
8003
- },
8004
- "lead-qualification": {
8005
- completedAction: "Review qualification signals, update CRM fields, and route the lead to the right owner.",
8006
- completedDescription: "The call completed a lead-qualification flow and should be reviewed for sales follow-up.",
8007
- completedKind: "lead-qualification",
8008
- completedTitle: "Review qualified lead",
8009
- defaultCompletedCreatesTask: true,
8010
- defaultDueInMs: 15 * 60000,
8011
- defaultPriority: "high",
8012
- defaultQueue: "sales-leads",
8013
- description: "Creates sales follow-up work for completed qualification calls and fast callbacks for missed leads.",
8014
- escalationQueue: "sales-escalations"
8015
- },
8016
- "support-triage": {
8017
- completedAction: "Review the triage result, confirm the support category, and route any unresolved issue.",
8018
- completedDescription: "The call completed support triage and may need queue routing or human follow-up.",
8019
- completedKind: "support-triage",
8020
- completedTitle: "Review support triage",
8021
- defaultCompletedCreatesTask: true,
8022
- defaultDueInMs: 20 * 60000,
8023
- defaultPriority: "normal",
8024
- defaultQueue: "support-triage",
8025
- description: "Creates support triage work for completed calls and urgent escalation/callback work for unresolved callers.",
8026
- escalationQueue: "support-escalations"
8027
- },
8028
- "voicemail-callback": {
8029
- completedAction: "No callback is required for completed calls.",
8030
- completedDescription: "The call completed without requiring voicemail follow-up.",
8031
- completedKind: "callback",
8032
- completedTitle: "Completed call",
8033
- defaultCompletedCreatesTask: false,
8034
- defaultDueInMs: 15 * 60000,
8035
- defaultPriority: "high",
8036
- defaultQueue: "callbacks",
8037
- description: "Creates callback work for voicemail, no-answer, failed, or escalated calls while ignoring completed calls.",
8038
- escalationQueue: "callback-escalations"
8039
- },
8040
- "warm-transfer": {
8041
- completedAction: "Confirm the handoff target received the caller context and close the transfer loop.",
8042
- completedDescription: "The call is part of a warm-transfer flow and should be verified downstream.",
8043
- completedKind: "transfer-check",
8044
- completedTitle: "Verify warm transfer",
8045
- defaultCompletedCreatesTask: false,
8046
- defaultDueInMs: 10 * 60000,
8047
- defaultPriority: "normal",
8048
- defaultQueue: "transfer-verification",
8049
- description: "Creates transfer verification work for transferred calls and escalation work when the handoff fails.",
8050
- escalationQueue: "transfer-escalations"
8051
- }
8052
- };
8053
- var buildRecipeTask = (input) => {
8054
- const createdAt = input.review.generatedAt ?? Date.now();
8055
- const queue = input.options.queue ?? input.defaults.defaultQueue;
8056
- const target = input.options.target ?? input.review.postCall?.target;
8057
- const common = {
8058
- assignee: input.options.assignee,
8059
- createdAt,
8060
- history: [
8061
- {
8062
- actor: "system",
8063
- at: createdAt,
8064
- detail: input.review.postCall?.summary,
8065
- type: "created"
8066
- }
8067
- ],
8068
- id: `${input.review.id}:${input.defaults.completedKind}`,
8069
- intakeId: input.review.id,
8070
- outcome: input.review.summary.outcome,
8071
- priority: input.options.priority ?? input.defaults.defaultPriority,
8072
- queue,
8073
- reviewId: input.review.id,
8074
- status: "open",
8075
- target,
8076
- updatedAt: createdAt
8077
- };
8078
- switch (input.disposition) {
8079
- case "completed":
8080
- if (!(input.options.completedCreatesTask ?? input.defaults.defaultCompletedCreatesTask)) {
8081
- return null;
8082
- }
8083
- return {
8084
- ...common,
8085
- description: input.defaults.completedDescription,
8086
- kind: input.defaults.completedKind,
8087
- recommendedAction: input.defaults.completedAction,
8088
- title: target ? `${input.defaults.completedTitle}: ${target}` : input.defaults.completedTitle
8089
- };
8090
- case "voicemail":
8091
- return {
8092
- ...common,
8093
- description: input.review.postCall?.summary ?? "The caller reached voicemail and needs a callback.",
8094
- id: `${input.review.id}:callback`,
8095
- kind: "callback",
8096
- recommendedAction: input.review.postCall?.recommendedAction ?? "Call the customer back and continue the original flow.",
8097
- title: target ? `Call back ${target}` : "Call back voicemail lead"
8098
- };
8099
- case "no-answer":
8100
- return {
8101
- ...common,
8102
- description: input.review.postCall?.summary ?? "The call did not reach a live respondent and should be retried.",
8103
- id: `${input.review.id}:retry`,
8104
- kind: "callback",
8105
- recommendedAction: input.review.postCall?.recommendedAction ?? "Retry the call or schedule a callback.",
8106
- title: "Retry no-answer call"
8107
- };
8108
- case "transferred":
8109
- return {
8110
- ...common,
8111
- description: input.review.postCall?.summary ?? "The call was transferred and should be verified downstream.",
8112
- id: `${input.review.id}:transfer-check`,
8113
- kind: "transfer-check",
8114
- recommendedAction: input.review.postCall?.recommendedAction ?? "Confirm the receiving team got the caller context.",
8115
- title: target ? `Verify transfer to ${target}` : "Verify call transfer"
8116
- };
8117
- case "escalated":
8118
- return {
8119
- ...common,
8120
- description: input.review.postCall?.summary ?? "The call escalated and needs human review.",
8121
- id: `${input.review.id}:escalation`,
8122
- kind: "escalation",
8123
- priority: "urgent",
8124
- queue: input.options.escalationQueue ?? input.defaults.escalationQueue,
8125
- assignee: input.options.escalationAssignee ?? input.options.assignee,
8126
- recommendedAction: input.review.postCall?.recommendedAction ?? "Review the escalated call and respond immediately.",
8127
- title: "Review escalated call"
8128
- };
8129
- case "failed":
8130
- case "closed":
8131
- return {
8132
- ...common,
8133
- description: input.review.postCall?.summary ?? "The call ended before successful completion and needs review.",
8134
- id: `${input.review.id}:retry-review`,
8135
- kind: "retry-review",
8136
- priority: "high",
8137
- recommendedAction: input.review.postCall?.recommendedAction ?? "Inspect the call and decide whether to retry, escalate, or close.",
8138
- title: "Inspect incomplete call"
8139
- };
8140
- default:
8141
- return null;
8142
- }
8143
- };
8144
- var resolveVoiceOutcomeRecipe = (name, options = {}) => {
8145
- const defaults = RECIPE_DEFAULTS[name];
8146
- const taskPolicies = {
8147
- completed: {
8148
- assignee: options.assignee,
8149
- dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
8150
- name: `${name}-completed`,
8151
- priority: options.priority ?? defaults.defaultPriority,
8152
- queue: options.queue ?? defaults.defaultQueue
8153
- },
8154
- escalated: {
8155
- assignee: options.escalationAssignee ?? options.assignee,
8156
- dueInMs: Math.min(options.dueInMs ?? defaults.defaultDueInMs, 10 * 60000),
8157
- name: `${name}-escalation`,
8158
- priority: "urgent",
8159
- queue: options.escalationQueue ?? defaults.escalationQueue
8160
- },
8161
- failed: {
8162
- assignee: options.assignee,
8163
- dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
8164
- name: `${name}-failed-review`,
8165
- priority: "high",
8166
- queue: options.queue ?? defaults.defaultQueue
8167
- },
8168
- "no-answer": {
8169
- assignee: options.assignee,
8170
- dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
8171
- name: `${name}-no-answer`,
8172
- priority: options.priority ?? defaults.defaultPriority,
8173
- queue: options.queue ?? defaults.defaultQueue
8174
- },
8175
- transferred: {
8176
- assignee: options.assignee,
8177
- dueInMs: Math.min(options.dueInMs ?? defaults.defaultDueInMs, 20 * 60000),
8178
- name: `${name}-transfer-check`,
8179
- priority: options.priority ?? defaults.defaultPriority,
8180
- queue: name === "warm-transfer" ? options.queue ?? defaults.defaultQueue : "transfer-verification"
8181
- },
8182
- voicemail: {
8183
- assignee: options.assignee,
8184
- dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
8185
- name: `${name}-voicemail`,
8186
- priority: options.priority ?? defaults.defaultPriority,
8187
- queue: options.queue ?? defaults.defaultQueue
8188
- }
8189
- };
8190
- const taskAssignmentRules = [
8191
- {
8192
- assign: options.escalationAssignee ?? options.assignee,
8193
- description: `Route urgent ${name} work to the escalation lane.`,
8194
- name: `${name}-urgent-routing`,
8195
- queue: options.escalationQueue ?? defaults.escalationQueue,
8196
- when: {
8197
- priority: "urgent"
8198
- }
8199
- }
8200
- ].filter((rule) => rule.assign || rule.queue);
8201
- return {
8202
- createTaskFromReview: ({ disposition, review }) => buildRecipeTask({
8203
- defaults,
8204
- disposition,
8205
- options,
8206
- review
8207
- }),
8208
- description: defaults.description,
8209
- name,
8210
- taskAssignmentRules,
8211
- taskPolicies
8212
- };
8213
- };
8214
8551
  // src/correction.ts
8215
8552
  var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8216
8553
  var buildAliasMatcher = (alias) => new RegExp(`(?<![\\p{L}\\p{N}'])${escapeRegExp(alias)}(?![\\p{L}\\p{N}'])`, "giu");
@@ -8990,6 +9327,7 @@ export {
8990
9327
  summarizeVoiceOpsTaskQueue,
8991
9328
  summarizeVoiceOpsTaskAnalytics,
8992
9329
  summarizeVoiceIntegrationEvents,
9330
+ summarizeVoiceAssistantRuns,
8993
9331
  startVoiceOpsTask,
8994
9332
  shapeTelephonyAssistantText,
8995
9333
  selectVoiceTraceEventsForPrune,
@@ -9097,11 +9435,13 @@ export {
9097
9435
  createVoiceFileExternalObjectMapStore,
9098
9436
  createVoiceExternalObjectMapId,
9099
9437
  createVoiceExternalObjectMap,
9438
+ createVoiceExperiment,
9100
9439
  createVoiceCallReviewRecorder,
9101
9440
  createVoiceCallReviewFromSession,
9102
9441
  createVoiceCallReviewFromLiveTelephonyReport,
9103
9442
  createVoiceCallCompletedEvent,
9104
9443
  createVoiceCRMActivitySink,
9444
+ createVoiceAssistant,
9105
9445
  createVoiceAgentTool,
9106
9446
  createVoiceAgentSquad,
9107
9447
  createVoiceAgent,
package/dist/trace.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type VoiceTraceEventType = 'agent.handoff' | 'agent.model' | 'agent.result' | 'agent.tool' | 'call.lifecycle' | 'session.error' | 'turn.assistant' | 'turn.committed' | 'turn.cost' | 'turn.transcript';
1
+ export type VoiceTraceEventType = 'assistant.guardrail' | 'assistant.run' | 'agent.handoff' | 'agent.model' | 'agent.result' | 'agent.tool' | 'call.lifecycle' | 'session.error' | 'turn.assistant' | 'turn.committed' | 'turn.cost' | 'turn.transcript';
2
2
  export type VoiceTraceEvent<TPayload extends Record<string, unknown> = Record<string, unknown>> = {
3
3
  at: number;
4
4
  id?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.1",
3
+ "version": "0.0.22-beta.3",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",