@absolutejs/voice 0.0.22-beta.125 → 0.0.22-beta.127
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 +236 -26
- package/dist/agent.d.ts +5 -0
- package/dist/angular/index.d.ts +1 -1
- package/dist/angular/index.js +16 -16
- package/dist/angular/voice-ops-status.service.d.ts +12 -0
- package/dist/audit.d.ts +128 -0
- package/dist/auditDeliveryRoutes.d.ts +85 -0
- package/dist/auditExport.d.ts +34 -0
- package/dist/auditRoutes.d.ts +66 -0
- package/dist/auditSinks.d.ts +133 -0
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +11 -11
- package/dist/client/opsStatus.d.ts +19 -0
- package/dist/client/opsStatusWidget.d.ts +7 -7
- package/dist/dataControl.d.ts +47 -0
- package/dist/demoReadyRoutes.d.ts +98 -0
- package/dist/fileStore.d.ts +8 -2
- package/dist/index.d.ts +27 -6
- package/dist/index.js +14600 -12518
- package/dist/opsStatus.d.ts +65 -0
- package/dist/opsStatusRoutes.d.ts +33 -0
- package/dist/phoneAgent.d.ts +4 -0
- package/dist/phoneAgentProductionSmoke.d.ts +115 -0
- package/dist/postgresStore.d.ts +8 -2
- package/dist/productionReadiness.d.ts +94 -0
- package/dist/providerRoutingContract.d.ts +38 -0
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +16 -16
- package/dist/react/useVoiceOpsStatus.d.ts +8 -0
- package/dist/sqliteStore.d.ts +8 -2
- package/dist/svelte/createVoiceOpsStatus.d.ts +2 -2
- package/dist/svelte/index.d.ts +0 -1
- package/dist/svelte/index.js +72 -75
- package/dist/traceDeliveryRoutes.d.ts +86 -0
- package/dist/vue/index.d.ts +1 -1
- package/dist/vue/index.js +15 -15
- package/dist/vue/useVoiceOpsStatus.d.ts +9 -0
- package/package.json +1 -1
- package/dist/angular/voice-app-kit-status.service.d.ts +0 -12
- package/dist/appKit.d.ts +0 -100
- package/dist/client/appKitStatus.d.ts +0 -19
- package/dist/react/useVoiceAppKitStatus.d.ts +0 -8
- package/dist/svelte/createVoiceAppKitStatus.d.ts +0 -8
- package/dist/vue/useVoiceAppKitStatus.d.ts +0 -9
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Use it when you want Vapi/Retell/Bland-style voice-agent capability, but you wan
|
|
|
11
11
|
- Self-hosted by default: your app owns sessions, traces, reviews, tasks, handoffs, retention, and provider keys.
|
|
12
12
|
- Provider-neutral: use Deepgram, AssemblyAI, OpenAI, Anthropic, Gemini, ElevenLabs-style TTS, or your own adapters without rewriting app workflow code.
|
|
13
13
|
- Browser and phone surfaces: mount browser WebSocket voice routes plus Twilio, Telnyx, and Plivo telephony routes from the same package.
|
|
14
|
-
- Production proof:
|
|
14
|
+
- Production proof: ops status, production readiness, turn quality, turn latency, live browser p50/p95 latency, trace timelines, evals, fixtures, and contracts are package primitives.
|
|
15
15
|
- Framework parity: React, Vue, Svelte, Angular, HTML, HTMX, and plain client entrypoints share the same core behavior.
|
|
16
16
|
- No hosted platform tax: AbsoluteJS Voice does not add a mandatory per-minute orchestration fee between your app and your providers.
|
|
17
17
|
|
|
@@ -21,7 +21,7 @@ Pick the path that matches what you are building:
|
|
|
21
21
|
|
|
22
22
|
- Browser voice agent: mount `voice(...)`, choose an STT adapter, and use the React/Vue/Svelte/Angular/HTML/HTMX client helpers for mic, transcript, reconnect, and status UI.
|
|
23
23
|
- Phone voice agent: mount Twilio, Telnyx, or Plivo routes, normalize carrier outcomes, inspect carrier readiness, and persist call lifecycle traces.
|
|
24
|
-
- Production readiness: mount `
|
|
24
|
+
- Production readiness: mount the status and proof primitives you need, such as `createVoiceOpsStatusRoutes(...)`, `createVoiceProductionReadinessRoutes(...)`, quality routes, trace routes, eval routes, and smoke contracts.
|
|
25
25
|
- Provider routing and fallback: use LLM/STT/TTS provider routers, provider health, provider simulation controls, and cost/latency-aware routing policies.
|
|
26
26
|
- Evals and simulation: mount `createVoiceSimulationSuiteRoutes(...)` to run scenario fixtures, workflow contracts, tool contracts, outcome contracts, baseline comparisons, and saved benchmark artifacts before live traffic.
|
|
27
27
|
|
|
@@ -117,14 +117,17 @@ const app = new Elysia()
|
|
|
117
117
|
|
|
118
118
|
## Production Readiness Path
|
|
119
119
|
|
|
120
|
-
Once the basic route works, mount the
|
|
120
|
+
Once the basic route works, mount the proof routes you need. These give you the self-hosted operational surfaces that hosted platforms usually make mandatory, without forcing a bundled app kit:
|
|
121
121
|
|
|
122
122
|
```ts
|
|
123
123
|
import {
|
|
124
|
-
|
|
124
|
+
createVoiceAuditDeliveryRoutes,
|
|
125
|
+
createVoiceDemoReadyRoutes,
|
|
125
126
|
createVoiceFileRuntimeStorage,
|
|
126
127
|
createVoiceLiveLatencyRoutes,
|
|
128
|
+
createVoiceOpsStatusRoutes,
|
|
127
129
|
createVoiceProductionReadinessRoutes,
|
|
130
|
+
createVoiceTraceDeliveryRoutes,
|
|
128
131
|
createVoiceTraceTimelineRoutes,
|
|
129
132
|
createVoiceTurnLatencyRoutes,
|
|
130
133
|
createVoiceTurnQualityRoutes
|
|
@@ -136,11 +139,11 @@ const runtime = createVoiceFileRuntimeStorage({
|
|
|
136
139
|
|
|
137
140
|
app
|
|
138
141
|
.use(
|
|
139
|
-
|
|
142
|
+
createVoiceOpsStatusRoutes({
|
|
140
143
|
store: runtime.traces,
|
|
141
144
|
llmProviders: ['openai', 'anthropic', 'gemini'],
|
|
142
145
|
sttProviders: ['deepgram', 'assemblyai']
|
|
143
|
-
})
|
|
146
|
+
})
|
|
144
147
|
)
|
|
145
148
|
.use(
|
|
146
149
|
createVoiceTurnLatencyRoutes({
|
|
@@ -173,18 +176,40 @@ app
|
|
|
173
176
|
)
|
|
174
177
|
.use(
|
|
175
178
|
createVoiceProductionReadinessRoutes({
|
|
179
|
+
audit: runtime.audit,
|
|
180
|
+
auditDeliveries: runtime.auditDeliveries,
|
|
176
181
|
htmlPath: '/production-readiness',
|
|
177
182
|
path: '/api/production-readiness',
|
|
178
|
-
store: runtime.traces
|
|
183
|
+
store: runtime.traces,
|
|
184
|
+
traceDeliveries: runtime.traceDeliveries
|
|
185
|
+
})
|
|
186
|
+
)
|
|
187
|
+
.use(
|
|
188
|
+
createVoiceAuditDeliveryRoutes({
|
|
189
|
+
htmlPath: '/audit/deliveries',
|
|
190
|
+
path: '/api/voice-audit-deliveries',
|
|
191
|
+
store: runtime.auditDeliveries
|
|
192
|
+
})
|
|
193
|
+
)
|
|
194
|
+
.use(
|
|
195
|
+
createVoiceTraceDeliveryRoutes({
|
|
196
|
+
htmlPath: '/traces/deliveries',
|
|
197
|
+
path: '/api/voice-trace-deliveries',
|
|
198
|
+
store: runtime.traceDeliveries
|
|
179
199
|
})
|
|
180
200
|
);
|
|
181
201
|
```
|
|
182
202
|
|
|
183
203
|
Recommended proof routes:
|
|
184
204
|
|
|
185
|
-
- `/
|
|
205
|
+
- `/api/voice/ops-status`: compact status for hooks, widgets, and customer-facing demos.
|
|
206
|
+
- `/api/voice/ops-status/html`: HTML status card for quick internal review.
|
|
207
|
+
- `/demo-ready`: customer-facing demo readiness checklist.
|
|
186
208
|
- `/production-readiness`: production gate summary.
|
|
209
|
+
- `/audit/deliveries`: audit sink export queue and failed delivery details.
|
|
210
|
+
- `/voice/phone/smoke-contract`: trace-backed phone-agent production smoke proof.
|
|
187
211
|
- `/traces`: per-session trace timelines.
|
|
212
|
+
- `/traces/deliveries`: trace sink export queue and failed delivery details.
|
|
188
213
|
- `/turn-latency`: server-side turn-stage latency.
|
|
189
214
|
- `/live-latency`: browser-measured speech-to-assistant p50/p95 latency.
|
|
190
215
|
- `/turn-quality`: STT confidence, correction, fallback, and transcript diagnostics.
|
|
@@ -231,7 +256,8 @@ Use `createVoicePhoneAgent(...)` when the agent needs to answer or place calls t
|
|
|
231
256
|
```ts
|
|
232
257
|
import {
|
|
233
258
|
createVoicePhoneAgent,
|
|
234
|
-
createVoiceTelephonyOutcomePolicy
|
|
259
|
+
createVoiceTelephonyOutcomePolicy,
|
|
260
|
+
runVoicePhoneAgentProductionSmokeContract
|
|
235
261
|
} from '@absolutejs/voice';
|
|
236
262
|
import { deepgram } from '@absolutejs/voice-deepgram';
|
|
237
263
|
|
|
@@ -250,6 +276,19 @@ app
|
|
|
250
276
|
path: '/api/carriers',
|
|
251
277
|
title: 'AbsoluteJS Voice Carrier Matrix'
|
|
252
278
|
},
|
|
279
|
+
productionSmoke: {
|
|
280
|
+
maxAgeMs: 24 * 60 * 60 * 1000,
|
|
281
|
+
required: [
|
|
282
|
+
'carrier-contract',
|
|
283
|
+
'media-started',
|
|
284
|
+
'transcript',
|
|
285
|
+
'assistant-response',
|
|
286
|
+
'lifecycle-outcome',
|
|
287
|
+
'no-session-error',
|
|
288
|
+
'fresh-trace'
|
|
289
|
+
],
|
|
290
|
+
store: runtime.traces
|
|
291
|
+
},
|
|
253
292
|
carriers: [
|
|
254
293
|
{
|
|
255
294
|
provider: 'twilio',
|
|
@@ -284,6 +323,8 @@ The wrapper mounts selected carrier routes plus two proof surfaces:
|
|
|
284
323
|
- `/api/voice/phone/setup?format=html`: copy/paste setup page for carrier dashboards.
|
|
285
324
|
- `/api/carriers`: carrier matrix JSON for Twilio, Telnyx, and Plivo.
|
|
286
325
|
- `/api/carriers?format=html`: side-by-side carrier readiness matrix.
|
|
326
|
+
- `/api/voice/phone/smoke-contract?sessionId=...`: trace-backed production smoke contract.
|
|
327
|
+
- `/voice/phone/smoke-contract?sessionId=...`: HTML production smoke contract.
|
|
287
328
|
|
|
288
329
|
The setup page tells you exactly what to copy into the carrier dashboard:
|
|
289
330
|
|
|
@@ -315,36 +356,89 @@ The phone-agent report normalizes the lifecycle schema across carriers:
|
|
|
315
356
|
|
|
316
357
|
That is the important Vapi/Retell/Bland gap this primitive closes: a team can mount one phone-agent entrypoint, bring its own carrier account, verify readiness before live calls, and keep call traces and lifecycle outcomes inside its own AbsoluteJS app. Telnyx and Plivo use the same wrapper with `{ provider: 'telnyx', options: ... }` or `{ provider: 'plivo', options: ... }`. The lower-level `createTwilioVoiceRoutes(...)`, `createTelnyxVoiceRoutes(...)`, and `createPlivoVoiceRoutes(...)` helpers remain available when you need carrier-specific control.
|
|
317
358
|
|
|
318
|
-
|
|
359
|
+
After running a real smoke call, certify the phone-agent path from traces:
|
|
319
360
|
|
|
320
|
-
|
|
361
|
+
```ts
|
|
362
|
+
const smoke = await runVoicePhoneAgentProductionSmokeContract({
|
|
363
|
+
maxAgeMs: 24 * 60 * 60 * 1000,
|
|
364
|
+
required: [
|
|
365
|
+
'media-started',
|
|
366
|
+
'transcript',
|
|
367
|
+
'assistant-response',
|
|
368
|
+
'lifecycle-outcome',
|
|
369
|
+
'no-session-error',
|
|
370
|
+
'fresh-trace'
|
|
371
|
+
],
|
|
372
|
+
sessionId: 'phone-smoke-session',
|
|
373
|
+
store: runtime.traces
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
if (!smoke.pass) {
|
|
377
|
+
throw new Error(smoke.issues.map((issue) => issue.message).join('\n'));
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Pass those reports into production readiness through `phoneAgentSmokes`. This makes deployment fail when the carrier setup exists but the actual phone-agent call path did not produce media start, transcript, assistant response, terminal lifecycle outcome, and clean trace evidence.
|
|
382
|
+
|
|
383
|
+
When `productionSmoke` is enabled on `createVoicePhoneAgent(...)`, the wrapper mounts `/api/voice/phone/smoke-contract?sessionId=...` for JSON and `/voice/phone/smoke-contract?sessionId=...` for HTML. It also derives carrier contract evidence from the existing carrier matrix unless you provide a custom `getContract`.
|
|
384
|
+
|
|
385
|
+
## Ops Status Hooks And Widgets
|
|
386
|
+
|
|
387
|
+
Use `createVoiceOpsStatusRoutes(...)` when you want a small status endpoint for demos, admin pages, and framework widgets. It is intentionally not a route bundle: mount quality gates, eval routes, provider health, session replay, phone-agent smoke proof, handoff health, and diagnostics explicitly when your app needs them.
|
|
321
388
|
|
|
322
389
|
```ts
|
|
323
|
-
import {
|
|
390
|
+
import {
|
|
391
|
+
createVoiceDemoReadyRoutes,
|
|
392
|
+
createVoiceFileRuntimeStorage,
|
|
393
|
+
createVoiceOpsStatusRoutes,
|
|
394
|
+
summarizeVoiceOpsStatus
|
|
395
|
+
} from '@absolutejs/voice';
|
|
324
396
|
|
|
325
397
|
const runtime = createVoiceFileRuntimeStorage({ directory: '.voice-runtime/support' });
|
|
326
398
|
|
|
327
399
|
app.use(
|
|
328
|
-
|
|
400
|
+
createVoiceOpsStatusRoutes({
|
|
329
401
|
store: runtime.traces,
|
|
330
402
|
llmProviders: ['openai', 'anthropic', 'gemini'],
|
|
331
403
|
sttProviders: ['deepgram', 'assemblyai']
|
|
332
|
-
})
|
|
404
|
+
})
|
|
333
405
|
);
|
|
334
406
|
```
|
|
335
407
|
|
|
336
|
-
The status endpoint is intentionally small enough for customer-facing demos. It can report fixture-backed workflow readiness while leaving deeper live quality/session failures visible on the
|
|
408
|
+
The status endpoint is intentionally small enough for customer-facing demos. It can report fixture-backed workflow readiness while leaving deeper live quality/session failures visible on the proof routes you mount separately.
|
|
409
|
+
|
|
410
|
+
For a single demo page that rolls up ops status, production readiness, phone setup, and phone smoke proof, mount `createVoiceDemoReadyRoutes(...)` with the same reports you already expose elsewhere:
|
|
337
411
|
|
|
338
412
|
```ts
|
|
339
413
|
app.use(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
414
|
+
createVoiceDemoReadyRoutes({
|
|
415
|
+
opsStatus: {
|
|
416
|
+
href: '/api/voice/ops-status',
|
|
417
|
+
load: () => summarizeVoiceOpsStatus(opsStatusOptions)
|
|
418
|
+
},
|
|
419
|
+
phoneSetup: {
|
|
420
|
+
href: '/api/voice/phone/setup?format=html',
|
|
421
|
+
load: () => phoneAgentSetupReport
|
|
422
|
+
},
|
|
423
|
+
phoneSmoke: {
|
|
424
|
+
href: '/voice/phone/smoke-contract',
|
|
425
|
+
load: () => phoneSmokeReport
|
|
344
426
|
},
|
|
345
|
-
|
|
427
|
+
productionReadiness: {
|
|
428
|
+
href: '/production-readiness',
|
|
429
|
+
load: () => productionReadinessReport
|
|
430
|
+
}
|
|
431
|
+
})
|
|
432
|
+
);
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
```ts
|
|
436
|
+
app.use(
|
|
437
|
+
createVoiceOpsStatusRoutes({
|
|
438
|
+
include: { quality: false, sessions: false },
|
|
439
|
+
preferFixtureWorkflows: true,
|
|
346
440
|
store: runtime.traces
|
|
347
|
-
})
|
|
441
|
+
})
|
|
348
442
|
);
|
|
349
443
|
```
|
|
350
444
|
|
|
@@ -377,7 +471,7 @@ import { VoiceOpsStatus } from '@absolutejs/voice/vue';
|
|
|
377
471
|
import { onDestroy, onMount } from 'svelte';
|
|
378
472
|
import { createVoiceOpsStatus } from '@absolutejs/voice/svelte';
|
|
379
473
|
|
|
380
|
-
const status = createVoiceOpsStatus('/
|
|
474
|
+
const status = createVoiceOpsStatus('/api/voice/ops-status', { intervalMs: 5000 });
|
|
381
475
|
let html = '';
|
|
382
476
|
onMount(() => status.subscribe(() => (html = status.getHTML())));
|
|
383
477
|
onDestroy(() => status.close());
|
|
@@ -389,9 +483,9 @@ import { VoiceOpsStatus } from '@absolutejs/voice/vue';
|
|
|
389
483
|
### Angular Status Widget
|
|
390
484
|
|
|
391
485
|
```ts
|
|
392
|
-
import {
|
|
486
|
+
import { VoiceOpsStatusService } from '@absolutejs/voice/angular';
|
|
393
487
|
|
|
394
|
-
status = inject(
|
|
488
|
+
status = inject(VoiceOpsStatusService).connect('/api/voice/ops-status', {
|
|
395
489
|
intervalMs: 5000
|
|
396
490
|
});
|
|
397
491
|
```
|
|
@@ -408,7 +502,7 @@ status = inject(VoiceAppKitStatusService).connect('/app-kit/status', {
|
|
|
408
502
|
<script type="module">
|
|
409
503
|
import { mountVoiceOpsStatus } from '@absolutejs/voice/client';
|
|
410
504
|
|
|
411
|
-
mountVoiceOpsStatus(document.querySelector('#voice-ops-status'), '/
|
|
505
|
+
mountVoiceOpsStatus(document.querySelector('#voice-ops-status'), '/api/voice/ops-status', {
|
|
412
506
|
intervalMs: 5000
|
|
413
507
|
});
|
|
414
508
|
</script>
|
|
@@ -648,13 +742,22 @@ Use trace stores when you want every call to be inspectable outside a hosted pla
|
|
|
648
742
|
```ts
|
|
649
743
|
import {
|
|
650
744
|
buildVoiceTraceReplay,
|
|
745
|
+
buildVoiceAuditExport,
|
|
746
|
+
createVoiceAuditHTTPSink,
|
|
747
|
+
createVoiceAuditLogger,
|
|
748
|
+
createVoiceAuditSinkDeliveryWorker,
|
|
749
|
+
createVoiceAuditSinkStore,
|
|
750
|
+
createVoiceAuditTrailRoutes,
|
|
651
751
|
createVoiceAgent,
|
|
652
752
|
createVoiceFileRuntimeStorage,
|
|
653
753
|
createVoiceRedisTaskLeaseCoordinator,
|
|
754
|
+
createVoiceTraceDeliveryRoutes,
|
|
654
755
|
createVoiceTraceHTTPSink,
|
|
655
756
|
createVoiceTraceSinkStore,
|
|
656
757
|
createVoiceTraceSinkDeliveryWorker,
|
|
758
|
+
buildVoiceDataRetentionPlan,
|
|
657
759
|
exportVoiceTrace,
|
|
760
|
+
applyVoiceDataRetentionPolicy,
|
|
658
761
|
pruneVoiceTraceEvents,
|
|
659
762
|
voice
|
|
660
763
|
} from '@absolutejs/voice';
|
|
@@ -666,6 +769,30 @@ const runtimeStorage = createVoiceFileRuntimeStorage({
|
|
|
666
769
|
const redisLeases = createVoiceRedisTaskLeaseCoordinator({
|
|
667
770
|
url: process.env.REDIS_URL
|
|
668
771
|
});
|
|
772
|
+
const auditStore = createVoiceAuditSinkStore({
|
|
773
|
+
store: runtimeStorage.audit,
|
|
774
|
+
deliveryQueue: runtimeStorage.auditDeliveries,
|
|
775
|
+
sinks: [
|
|
776
|
+
createVoiceAuditHTTPSink({
|
|
777
|
+
id: 'security-warehouse',
|
|
778
|
+
signingSecret: process.env.VOICE_AUDIT_SINK_SECRET,
|
|
779
|
+
url: process.env.VOICE_AUDIT_SINK_URL!
|
|
780
|
+
})
|
|
781
|
+
]
|
|
782
|
+
});
|
|
783
|
+
const audit = createVoiceAuditLogger(auditStore);
|
|
784
|
+
const auditSinkWorker = createVoiceAuditSinkDeliveryWorker({
|
|
785
|
+
deliveries: runtimeStorage.auditDeliveries,
|
|
786
|
+
leases: redisLeases,
|
|
787
|
+
sinks: [
|
|
788
|
+
createVoiceAuditHTTPSink({
|
|
789
|
+
id: 'security-warehouse',
|
|
790
|
+
signingSecret: process.env.VOICE_AUDIT_SINK_SECRET,
|
|
791
|
+
url: process.env.VOICE_AUDIT_SINK_URL!
|
|
792
|
+
})
|
|
793
|
+
],
|
|
794
|
+
workerId: 'audit-sink-worker'
|
|
795
|
+
});
|
|
669
796
|
const trace = createVoiceTraceSinkStore({
|
|
670
797
|
store: runtimeStorage.traces,
|
|
671
798
|
deliveryQueue: runtimeStorage.traceDeliveries,
|
|
@@ -692,6 +819,9 @@ const traceSinkWorker = createVoiceTraceSinkDeliveryWorker({
|
|
|
692
819
|
|
|
693
820
|
const supportAgent = createVoiceAgent({
|
|
694
821
|
id: 'support',
|
|
822
|
+
audit,
|
|
823
|
+
auditProvider: 'openai',
|
|
824
|
+
auditModel: 'gpt-4.1',
|
|
695
825
|
trace,
|
|
696
826
|
model: {
|
|
697
827
|
async generate() {
|
|
@@ -708,6 +838,17 @@ voice({
|
|
|
708
838
|
onTurn: supportAgent.onTurn,
|
|
709
839
|
onComplete: async () => {}
|
|
710
840
|
});
|
|
841
|
+
app.use(
|
|
842
|
+
createVoiceAuditTrailRoutes({
|
|
843
|
+
store: runtimeStorage.audit
|
|
844
|
+
})
|
|
845
|
+
);
|
|
846
|
+
app.use(
|
|
847
|
+
createVoiceTraceDeliveryRoutes({
|
|
848
|
+
store: runtimeStorage.traceDeliveries,
|
|
849
|
+
worker: traceSinkWorker
|
|
850
|
+
})
|
|
851
|
+
);
|
|
711
852
|
|
|
712
853
|
const replay = await exportVoiceTrace({
|
|
713
854
|
store: runtimeStorage.traces,
|
|
@@ -729,18 +870,53 @@ await pruneVoiceTraceEvents({
|
|
|
729
870
|
store: runtimeStorage.traces,
|
|
730
871
|
before: Date.now() - 30 * 24 * 60 * 60 * 1000
|
|
731
872
|
});
|
|
873
|
+
|
|
874
|
+
await audit.operatorAction({
|
|
875
|
+
action: 'review.approve',
|
|
876
|
+
actor: { id: 'operator-123', kind: 'operator' },
|
|
877
|
+
resource: { id: 'review-123', type: 'review' }
|
|
878
|
+
});
|
|
732
879
|
```
|
|
733
880
|
|
|
734
881
|
`createVoiceMemoryTraceEventStore(...)`, `createVoiceFileTraceEventStore(...)`, `createVoiceSQLiteTraceEventStore(...)`, and `createVoicePostgresTraceEventStore(...)` all implement the same `VoiceTraceEventStore` contract. File, SQLite, and Postgres runtime storage expose `runtimeStorage.traces` and `runtimeStorage.traceDeliveries` alongside sessions, reviews, tasks, events, and external object mappings. Passing `trace` to `voice(...)` records session lifecycle, transcript, committed-turn, assistant, cost, and error events; passing it to agents records model passes, tools, results, and handoffs.
|
|
735
882
|
|
|
736
883
|
For self-hosted QA and support workflows, use `summarizeVoiceTrace(...)`, `evaluateVoiceTrace(...)`, `renderVoiceTraceMarkdown(...)`, `renderVoiceTraceHTML(...)`, or `buildVoiceTraceReplay(...)`. They turn raw trace events into portable artifacts you can attach to tickets, inspect locally, or fail in CI when a call has missing transcripts, missing turns, tool errors, session errors, or excessive handoffs.
|
|
737
884
|
|
|
738
|
-
For observability pipelines, wrap any trace store with `createVoiceTraceSinkStore(...)` and pass sinks such as `createVoiceTraceHTTPSink(...)`. The wrapper still writes to your normal file, SQLite, or Postgres store, then fans out appended events to your warehouse, logs, S3 bridge, or analytics endpoint. Use `awaitDelivery: true` only when you want trace delivery to block append completion. For durable delivery, pass `deliveryQueue` and run `createVoiceTraceSinkDeliveryWorker(...)` or `createVoiceTraceSinkDeliveryWorkerLoop(...)`; the worker uses the same Redis lease/idempotency primitives as ops workers and supports retries plus dead-letter stores.
|
|
885
|
+
For observability pipelines, wrap any trace store with `createVoiceTraceSinkStore(...)` and pass sinks such as `createVoiceTraceHTTPSink(...)`. The wrapper still writes to your normal file, SQLite, or Postgres store, then fans out appended events to your warehouse, logs, S3 bridge, or analytics endpoint. Use `awaitDelivery: true` only when you want trace delivery to block append completion. For durable delivery, pass `deliveryQueue` and run `createVoiceTraceSinkDeliveryWorker(...)` or `createVoiceTraceSinkDeliveryWorkerLoop(...)`; the worker uses the same Redis lease/idempotency primitives as ops workers and supports retries plus dead-letter stores. Mount `createVoiceTraceDeliveryRoutes({ store: runtimeStorage.traceDeliveries, worker })` to expose `/traces/deliveries`, `/api/voice-trace-deliveries`, and an explicit `POST /api/voice-trace-deliveries/drain` retry path.
|
|
739
886
|
|
|
740
887
|
When traces may leave your private runtime, pass `redact: true` or a redaction config to `exportVoiceTrace(...)`, `renderVoiceTraceMarkdown(...)`, `renderVoiceTraceHTML(...)`, or `buildVoiceTraceReplay(...)`. The built-in redactor scrubs common email addresses, phone numbers, and sensitive keys like `token`, `secret`, `password`, `apiKey`, `authorization`, `phone`, and `email`; you can pass custom keys or replacement text for stricter policies.
|
|
741
888
|
|
|
742
889
|
For retention jobs, `pruneVoiceTraceEvents(...)` works against any trace store. Use `dryRun: true` before deleting, filter by session, trace, scenario, turn, or event type, cap each run with `limit`, or keep only the newest N matching events with `keepNewest`.
|
|
743
890
|
|
|
891
|
+
For whole-runtime data control, use `buildVoiceDataRetentionPlan(...)` first and then `applyVoiceDataRetentionPolicy(...)` when the deletion set is correct. The policy works across stores exposed by file, SQLite, or Postgres runtime storage, including sessions, traces, trace deliveries, audit deliveries, reviews, ops tasks, integration events, and campaigns. A cutoff or per-scope `keepNewest` selector is required before anything is deleted, so an empty policy reports skipped scopes instead of wiping data.
|
|
892
|
+
|
|
893
|
+
```ts
|
|
894
|
+
const plan = await buildVoiceDataRetentionPlan({
|
|
895
|
+
before: Date.now() - 30 * 24 * 60 * 60 * 1000,
|
|
896
|
+
...runtimeStorage
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
console.log(plan.scopes);
|
|
900
|
+
|
|
901
|
+
await applyVoiceDataRetentionPolicy({
|
|
902
|
+
audit: runtimeStorage.audit,
|
|
903
|
+
before: Date.now() - 30 * 24 * 60 * 60 * 1000,
|
|
904
|
+
...runtimeStorage
|
|
905
|
+
});
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
Use `createVoiceAuditLogger(...)` when you need append-only compliance evidence outside call traces. The logger records provider calls, tool calls, handoffs, retention runs, and operator actions into `runtimeStorage.audit`, so self-hosted teams can prove who changed what, which provider ran, which tool fired, and what data-control policy deleted.
|
|
909
|
+
|
|
910
|
+
Pass `audit` directly to `createVoiceAgent(...)` to record model calls as provider-call audit events and tool executions as tool-call audit events. Pass it to `createVoiceAgentSquad(...)` to record squad handoffs automatically. Use `auditProvider` and `auditModel` on agents when you want readiness and compliance reports to show the actual model provider instead of the agent id.
|
|
911
|
+
|
|
912
|
+
For compliance pipelines, wrap any audit store with `createVoiceAuditSinkStore(...)` and pass sinks such as `createVoiceAuditHTTPSink(...)`. Audit sinks redact by default, support HMAC signing, retries, event-type filters, optional blocking delivery, durable delivery queues through `runtimeStorage.auditDeliveries`, and background workers through `createVoiceAuditSinkDeliveryWorker(...)` or `createVoiceAuditSinkDeliveryWorkerLoop(...)`. File, SQLite, and Postgres runtime storage all expose `auditDeliveries`, so teams can ship evidence to a SIEM, warehouse, or internal security service without a hosted dashboard. Mount `createVoiceAuditDeliveryRoutes({ store: runtimeStorage.auditDeliveries, worker })` to expose `/audit/deliveries`, `/api/voice-audit-deliveries`, and an explicit `POST /api/voice-audit-deliveries/drain` retry path.
|
|
913
|
+
|
|
914
|
+
Pass `audit: runtimeStorage.audit` into production readiness when audit coverage should block deploys. By default readiness requires provider-call, retention-policy, and operator-action audit evidence; retention-policy evidence must be from the last 7 days so a stale one-time audit event does not certify an active retention job. Override required event types or freshness with `audit: { store: runtimeStorage.audit, require: [{ type: 'retention.policy', maxAgeMs: ... }] }` when a deployment has different compliance gates. Pass `auditDeliveries: runtimeStorage.auditDeliveries` and `traceDeliveries: runtimeStorage.traceDeliveries` when sink export health should also block deploys; failed or dead-lettered deliveries fail readiness, pending deliveries warn, and pending deliveries older than the configured fail window fail readiness.
|
|
915
|
+
|
|
916
|
+
Mount `createVoiceAuditTrailRoutes(...)` to expose `/api/voice-audit` and `/audit` over the same store. File, SQLite, and Postgres runtime storage all expose `runtimeStorage.audit`. The JSON and HTML surfaces support filters like `type`, `outcome`, `actorId`, `resourceType`, `resourceId`, `sessionId`, `traceId`, and `limit`, so operators can search audit evidence without writing a custom viewer first.
|
|
917
|
+
|
|
918
|
+
Use `exportVoiceAuditTrail(...)` or `buildVoiceAuditExport(...)` when audit evidence needs to leave the app. Pass `redact: true` to scrub sensitive keys plus common email and phone patterns from payloads and metadata before generating JSON, Markdown, or HTML. Audit trail routes also expose redacted exports at `/api/voice-audit/export`, `/api/voice-audit/export?format=markdown`, `/api/voice-audit/export?format=html`, and `/audit/export`; export routes redact by default unless `redact=false` is passed.
|
|
919
|
+
|
|
744
920
|
## Production Voice Ops
|
|
745
921
|
|
|
746
922
|
The recommended production pattern is:
|
|
@@ -1534,6 +1710,40 @@ const policy = resolveVoiceProviderRoutingPolicyPreset('cost-cap', {
|
|
|
1534
1710
|
});
|
|
1535
1711
|
```
|
|
1536
1712
|
|
|
1713
|
+
Use `runVoiceProviderRoutingContract(...)` when provider fallback needs to be certified before production. The contract reads provider routing trace events and verifies the expected selected provider, fallback provider, status, and kind in order.
|
|
1714
|
+
|
|
1715
|
+
```ts
|
|
1716
|
+
import { runVoiceProviderRoutingContract } from '@absolutejs/voice';
|
|
1717
|
+
|
|
1718
|
+
const report = await runVoiceProviderRoutingContract({
|
|
1719
|
+
store: runtime.traces,
|
|
1720
|
+
contract: {
|
|
1721
|
+
id: 'openai-to-anthropic-fallback',
|
|
1722
|
+
expect: [
|
|
1723
|
+
{
|
|
1724
|
+
kind: 'llm',
|
|
1725
|
+
provider: 'openai',
|
|
1726
|
+
selectedProvider: 'openai',
|
|
1727
|
+
fallbackProvider: 'anthropic',
|
|
1728
|
+
status: 'error'
|
|
1729
|
+
},
|
|
1730
|
+
{
|
|
1731
|
+
kind: 'llm',
|
|
1732
|
+
provider: 'anthropic',
|
|
1733
|
+
selectedProvider: 'openai',
|
|
1734
|
+
status: 'fallback'
|
|
1735
|
+
}
|
|
1736
|
+
]
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
|
|
1740
|
+
if (!report.pass) {
|
|
1741
|
+
throw new Error(report.issues.map((issue) => issue.message).join('\n'));
|
|
1742
|
+
}
|
|
1743
|
+
```
|
|
1744
|
+
|
|
1745
|
+
Pass provider routing contract reports into production readiness through `providerRoutingContracts`. Readiness fails when a fallback contract fails, so model-routing regressions become deploy blockers instead of dashboard-only surprises.
|
|
1746
|
+
|
|
1537
1747
|
For full control, pass an object policy:
|
|
1538
1748
|
|
|
1539
1749
|
```ts
|
package/dist/agent.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { VoiceOnTurnObjectHandler, VoiceRouteResult, VoiceSessionHandle, VoiceSessionRecord, VoiceTurnRecord } from './types';
|
|
2
2
|
import type { VoiceTraceEventStore } from './trace';
|
|
3
3
|
import type { VoiceToolRuntime } from './toolRuntime';
|
|
4
|
+
import { type VoiceAuditEventStore, type VoiceAuditLogger } from './audit';
|
|
4
5
|
export type VoiceAgentMessageRole = 'assistant' | 'system' | 'tool' | 'user';
|
|
5
6
|
export type VoiceAgentMessage = {
|
|
6
7
|
content: string;
|
|
@@ -86,6 +87,9 @@ export type VoiceAgent<TContext = unknown, TSession extends VoiceSessionRecord =
|
|
|
86
87
|
}) => Promise<VoiceAgentRunResult<TResult>>;
|
|
87
88
|
};
|
|
88
89
|
export type VoiceAgentOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
90
|
+
audit?: VoiceAuditEventStore | VoiceAuditLogger;
|
|
91
|
+
auditModel?: string;
|
|
92
|
+
auditProvider?: string;
|
|
89
93
|
id: string;
|
|
90
94
|
maxToolRounds?: number;
|
|
91
95
|
model: VoiceAgentModel<TContext, TSession, TResult>;
|
|
@@ -99,6 +103,7 @@ export type VoiceAgentOptions<TContext = unknown, TSession extends VoiceSessionR
|
|
|
99
103
|
tools?: Array<VoiceAgentTool<TContext, TSession, Record<string, unknown>, unknown, TResult>>;
|
|
100
104
|
};
|
|
101
105
|
export type VoiceAgentSquadOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
106
|
+
audit?: VoiceAuditEventStore | VoiceAuditLogger;
|
|
102
107
|
agents: Array<VoiceAgent<TContext, TSession, TResult>>;
|
|
103
108
|
defaultAgentId: string;
|
|
104
109
|
handoffPolicy?: (input: {
|
package/dist/angular/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { VoiceOpsStatusService } from './voice-ops-status.service';
|
|
2
2
|
export { VoiceCampaignDialerProofService } from './voice-campaign-dialer-proof.service';
|
|
3
3
|
export { VoiceStreamService } from './voice-stream.service';
|
|
4
4
|
export { VoiceControllerService } from './voice-controller.service';
|
package/dist/angular/index.js
CHANGED
|
@@ -69,19 +69,19 @@ var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
|
69
69
|
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
// src/angular/voice-
|
|
72
|
+
// src/angular/voice-ops-status.service.ts
|
|
73
73
|
import { computed, Injectable, signal } from "@angular/core";
|
|
74
74
|
|
|
75
|
-
// src/client/
|
|
76
|
-
var
|
|
75
|
+
// src/client/opsStatus.ts
|
|
76
|
+
var fetchVoiceOpsStatus = async (path = "/api/voice/ops-status", options = {}) => {
|
|
77
77
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
78
78
|
const response = await fetchImpl(path);
|
|
79
79
|
if (!response.ok) {
|
|
80
|
-
throw new Error(`Voice
|
|
80
|
+
throw new Error(`Voice ops status failed: HTTP ${response.status}`);
|
|
81
81
|
}
|
|
82
82
|
return await response.json();
|
|
83
83
|
};
|
|
84
|
-
var
|
|
84
|
+
var createVoiceOpsStatusStore = (path = "/api/voice/ops-status", options = {}) => {
|
|
85
85
|
const listeners = new Set;
|
|
86
86
|
let closed = false;
|
|
87
87
|
let timer;
|
|
@@ -105,7 +105,7 @@ var createVoiceAppKitStatusStore = (path = "/app-kit/status", options = {}) => {
|
|
|
105
105
|
};
|
|
106
106
|
emit();
|
|
107
107
|
try {
|
|
108
|
-
const report = await
|
|
108
|
+
const report = await fetchVoiceOpsStatus(path, options);
|
|
109
109
|
snapshot = {
|
|
110
110
|
error: null,
|
|
111
111
|
isLoading: false,
|
|
@@ -151,15 +151,15 @@ var createVoiceAppKitStatusStore = (path = "/app-kit/status", options = {}) => {
|
|
|
151
151
|
};
|
|
152
152
|
};
|
|
153
153
|
|
|
154
|
-
// src/angular/voice-
|
|
154
|
+
// src/angular/voice-ops-status.service.ts
|
|
155
155
|
var _dec = [
|
|
156
156
|
Injectable({ providedIn: "root" })
|
|
157
157
|
];
|
|
158
158
|
var _init = __decoratorStart(undefined);
|
|
159
159
|
|
|
160
|
-
class
|
|
161
|
-
connect(path = "/
|
|
162
|
-
const store =
|
|
160
|
+
class VoiceOpsStatusService {
|
|
161
|
+
connect(path = "/api/voice/ops-status", options = {}) {
|
|
162
|
+
const store = createVoiceOpsStatusStore(path, options);
|
|
163
163
|
const errorSignal = signal(null);
|
|
164
164
|
const isLoadingSignal = signal(false);
|
|
165
165
|
const reportSignal = signal(undefined);
|
|
@@ -189,10 +189,10 @@ class VoiceAppKitStatusService {
|
|
|
189
189
|
};
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
|
-
|
|
193
|
-
__runInitializers(_init, 1,
|
|
194
|
-
__decoratorMetadata(_init,
|
|
195
|
-
let
|
|
192
|
+
VoiceOpsStatusService = __decorateElement(_init, 0, "VoiceOpsStatusService", _dec, VoiceOpsStatusService);
|
|
193
|
+
__runInitializers(_init, 1, VoiceOpsStatusService);
|
|
194
|
+
__decoratorMetadata(_init, VoiceOpsStatusService);
|
|
195
|
+
let _VoiceOpsStatusService = VoiceOpsStatusService;
|
|
196
196
|
// src/angular/voice-campaign-dialer-proof.service.ts
|
|
197
197
|
import { computed as computed2, Injectable as Injectable2, signal as signal2 } from "@angular/core";
|
|
198
198
|
|
|
@@ -2522,7 +2522,7 @@ export {
|
|
|
2522
2522
|
VoiceRoutingStatusService,
|
|
2523
2523
|
VoiceProviderStatusService,
|
|
2524
2524
|
VoiceProviderCapabilitiesService,
|
|
2525
|
+
VoiceOpsStatusService,
|
|
2525
2526
|
VoiceControllerService,
|
|
2526
|
-
VoiceCampaignDialerProofService
|
|
2527
|
-
VoiceAppKitStatusService
|
|
2527
|
+
VoiceCampaignDialerProofService
|
|
2528
2528
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type VoiceOpsStatusClientOptions } from '../client/opsStatus';
|
|
2
|
+
import type { VoiceOpsStatusReport } from '../opsStatus';
|
|
3
|
+
export declare class VoiceOpsStatusService {
|
|
4
|
+
connect(path?: string, options?: VoiceOpsStatusClientOptions): {
|
|
5
|
+
close: () => void;
|
|
6
|
+
error: import("@angular/core").Signal<string | null>;
|
|
7
|
+
isLoading: import("@angular/core").Signal<boolean>;
|
|
8
|
+
refresh: () => Promise<VoiceOpsStatusReport | undefined>;
|
|
9
|
+
report: import("@angular/core").Signal<VoiceOpsStatusReport | undefined>;
|
|
10
|
+
updatedAt: import("@angular/core").Signal<number | undefined>;
|
|
11
|
+
};
|
|
12
|
+
}
|