@absolutejs/voice 0.0.22-beta.204 → 0.0.22-beta.205

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
@@ -41,6 +41,51 @@ AbsoluteJS Voice is strongest when voice is part of your own product and you nee
41
41
 
42
42
  The goal is not to clone a hosted platform. The goal is to make AbsoluteJS the best place to build and operate self-hosted voice products.
43
43
 
44
+ ## Default Debug Path
45
+
46
+ Hosted platforms usually make the call log the center of debugging. AbsoluteJS Voice makes the operations record that center, while keeping the data and routes inside your app.
47
+
48
+ Mount `createVoiceOperationsRecordRoutes(...)` early in any serious voice app and use `/voice-operations/:sessionId` as the first support link for failed calls, bad transcripts, provider fallback, slow turns, handoff failures, campaign attempts, and post-call workflow issues.
49
+
50
+ The recommended investigation path is:
51
+
52
+ 1. Open `/production-readiness`, `/ops-recovery`, `/voice/simulations`, a tool contract report, or an outcome contract report.
53
+ 2. Follow the linked `/voice-operations/:sessionId` record for the impacted call or session.
54
+ 3. Inspect the transcript, trace timeline, replay links, provider decisions, tool calls, handoffs, reviews, tasks, audit events, integration events, and sink delivery attempts from one page.
55
+ 4. Use `/voice-operations/:sessionId/incident.md` when support, engineering, or a customer-facing handoff needs copyable incident context.
56
+
57
+ That is the Vapi-style call-log workflow without a vendor dashboard becoming the source of truth.
58
+
59
+ ## Switching From Vapi
60
+
61
+ If a team is already evaluating Vapi, map the dashboard concepts to AbsoluteJS primitives this way:
62
+
63
+ | Hosted voice-platform concept | AbsoluteJS Voice primitive |
64
+ | --- | --- |
65
+ | Assistant | `createVoiceAssistant(...)`, `createVoiceAgent(...)`, or `voice({ onTurn })` |
66
+ | Web call | `voice(...)` plus React, Vue, Svelte, Angular, HTML, HTMX, or client helpers |
67
+ | Phone call | `createVoicePhoneAgent(...)` with Twilio, Telnyx, or Plivo routes |
68
+ | Squads / multi-assistant routing | `createVoiceAgentSquad(...)` with `handoffPolicy`, `contextPolicy`, specialist tools, traces, and squad contracts |
69
+ | Tools / functions | Agent tools, tool runtime, `runVoiceToolContract(...)`, audit events, and integration events |
70
+ | Call logs | `/voice-operations/:sessionId`, trace timelines, replay links, reviews, tasks, audit, provider decisions, and delivery queues |
71
+ | Post-call analysis | reviews, outcomes, ops tasks, handoff deliveries, integration events, webhook/audit sinks, and outcome contracts |
72
+ | Simulation testing | `createVoiceSimulationSuiteRoutes(...)` with scenarios, fixtures, tool contracts, outcome contracts, and baseline comparison |
73
+ | Production monitoring | `createVoiceProductionReadinessRoutes(...)`, `createVoiceOpsRecoveryRoutes(...)`, ops status, latency SLO gates, provider health, and delivery runtime proof |
74
+ | Campaigns | `createVoiceCampaignRoutes(...)`, recipient import, scheduling controls, carrier dialer proof, and campaign readiness |
75
+ | Compliance controls | self-hosted storage, redaction defaults, retention plans, audit exports, data-control routes, and provider-key ownership |
76
+
77
+ The practical difference is ownership. In Vapi-style systems, the assistant, call log, tool execution, and operational dashboard live primarily in the vendor platform. With AbsoluteJS Voice, those same surfaces are route handlers, stores, reports, hooks, and contracts inside the AbsoluteJS app.
78
+
79
+ Use `createVoiceAssistant(...)` when you want a product-level assistant surface with tools, guardrails, experiments, tracing, reviews, tasks, and ops recipes. Drop down to `createVoiceAgent(...)` when you want a provider-neutral model/tool loop. Use raw `voice({ onTurn })` when you want the smallest possible browser voice route.
80
+
81
+ Use `createVoiceAgentSquad(...)` for Vapi Squads-style specialist routing without moving routing policy into a hosted dashboard. Each specialist owns its tools, `handoffPolicy` decides whether to allow, reroute, block, or escalate transfers, and `contextPolicy` decides what conversation context the next specialist receives. Squad traces and contracts make the handoff graph testable before production.
82
+
83
+ Use `createVoicePhoneAgent(...)` when the hosted-platform feature you need is "call this assistant by phone." The wrapper mounts carrier routes, setup pages, carrier matrix proof, and smoke-contract routes while still letting your app own Twilio, Telnyx, or Plivo credentials, webhooks, stream URLs, traces, and lifecycle outcomes.
84
+
85
+ Use operations records instead of hosted call logs. A proof failure should link to `/voice-operations/:sessionId`; the record then links the transcript, replay, provider choices, tool calls, handoffs, audit events, reviews, tasks, integration events, and delivery attempts. When someone needs a support handoff, send `/voice-operations/:sessionId/incident.md`.
86
+
87
+ Use simulation and contracts before live traffic. The simulation suite, tool contracts, outcome contracts, provider routing contracts, phone-agent smoke contracts, and production-readiness gates turn dashboard-only confidence into code-owned deploy evidence.
88
+
44
89
  ## Install
45
90
 
46
91
  ```bash
@@ -65,6 +110,178 @@ Common optional adapters:
65
110
  - `@absolutejs/voice-deepgram`
66
111
  - `@absolutejs/voice-assemblyai`
67
112
 
113
+ ## Fastest First Success
114
+
115
+ Use these paths when you want the smallest useful setup that still proves the app is production-shaped. The point is not to hide primitives; it is to mount the voice route plus the debug surfaces a real team needs immediately.
116
+
117
+ ### Browser Agent In 10 Minutes
118
+
119
+ ```ts
120
+ import { Elysia } from 'elysia';
121
+ import {
122
+ createVoiceFileRuntimeStorage,
123
+ createVoiceOperationsRecordRoutes,
124
+ createVoiceOpsStatusRoutes,
125
+ createVoiceProductionReadinessRoutes,
126
+ createVoiceTraceTimelineRoutes,
127
+ voice
128
+ } from '@absolutejs/voice';
129
+ import { deepgram } from '@absolutejs/voice-deepgram';
130
+
131
+ const runtime = createVoiceFileRuntimeStorage({
132
+ directory: '.voice-runtime/support'
133
+ });
134
+
135
+ export const app = new Elysia()
136
+ .use(
137
+ voice({
138
+ path: '/voice',
139
+ session: runtime.session,
140
+ trace: runtime.traces,
141
+ stt: deepgram({ apiKey: process.env.DEEPGRAM_API_KEY! }),
142
+ async onTurn({ turn }) {
143
+ return { assistantText: `I heard: ${turn.text}` };
144
+ },
145
+ onComplete: async () => {}
146
+ })
147
+ )
148
+ .use(
149
+ createVoiceOpsStatusRoutes({
150
+ path: '/api/voice/ops-status',
151
+ store: runtime.traces,
152
+ sttProviders: ['deepgram']
153
+ })
154
+ )
155
+ .use(
156
+ createVoiceTraceTimelineRoutes({
157
+ htmlPath: '/traces',
158
+ path: '/api/voice-traces',
159
+ store: runtime.traces
160
+ })
161
+ )
162
+ .use(
163
+ createVoiceOperationsRecordRoutes({
164
+ audit: runtime.audit,
165
+ htmlPath: '/voice-operations/:sessionId',
166
+ path: '/api/voice-operations/:sessionId',
167
+ store: runtime.traces
168
+ })
169
+ )
170
+ .use(
171
+ createVoiceProductionReadinessRoutes({
172
+ links: {
173
+ operationsRecords: '/voice-operations/:sessionId'
174
+ },
175
+ path: '/api/production-readiness',
176
+ htmlPath: '/production-readiness',
177
+ store: runtime.traces
178
+ })
179
+ );
180
+ ```
181
+
182
+ After one browser call, open:
183
+
184
+ - `/api/voice/ops-status`: compact health signal for UI/widgets.
185
+ - `/traces`: trace timeline by session.
186
+ - `/voice-operations/:sessionId`: call-log/debug record for the session.
187
+ - `/voice-operations/:sessionId/incident.md`: copyable incident handoff.
188
+ - `/production-readiness`: deploy gate summary.
189
+
190
+ ### Phone Agent In 20 Minutes
191
+
192
+ ```ts
193
+ import { Elysia } from 'elysia';
194
+ import {
195
+ createVoiceFileRuntimeStorage,
196
+ createVoiceOperationsRecordRoutes,
197
+ createVoicePhoneAgent,
198
+ createVoiceProductionReadinessRoutes,
199
+ createVoiceReadinessProfile,
200
+ createVoiceTelephonyOutcomePolicy
201
+ } from '@absolutejs/voice';
202
+ import { deepgram } from '@absolutejs/voice-deepgram';
203
+
204
+ const runtime = createVoiceFileRuntimeStorage({
205
+ directory: '.voice-runtime/support'
206
+ });
207
+ const outcomePolicy = createVoiceTelephonyOutcomePolicy({
208
+ transferTarget: process.env.VOICE_TRANSFER_TARGET
209
+ });
210
+
211
+ export const app = new Elysia()
212
+ .use(
213
+ createVoicePhoneAgent({
214
+ setup: { path: '/api/voice/phone/setup' },
215
+ matrix: { path: '/api/carriers' },
216
+ productionSmoke: {
217
+ maxAgeMs: 24 * 60 * 60 * 1000,
218
+ required: [
219
+ 'carrier-contract',
220
+ 'media-started',
221
+ 'transcript',
222
+ 'assistant-response',
223
+ 'lifecycle-outcome',
224
+ 'no-session-error',
225
+ 'fresh-trace'
226
+ ],
227
+ store: runtime.traces
228
+ },
229
+ carriers: [
230
+ {
231
+ provider: 'twilio',
232
+ options: {
233
+ context: {},
234
+ outcomePolicy,
235
+ session: runtime.session,
236
+ stt: deepgram({ apiKey: process.env.DEEPGRAM_API_KEY! }),
237
+ streamPath: '/api/voice/twilio/stream',
238
+ twiml: {
239
+ path: '/api/voice/twilio',
240
+ streamUrl: process.env.TWILIO_STREAM_URL
241
+ },
242
+ webhook: {
243
+ path: '/api/voice/twilio/webhook',
244
+ signingSecret: process.env.TWILIO_AUTH_TOKEN
245
+ },
246
+ async onTurn({ turn }) {
247
+ return { assistantText: `I heard: ${turn.text}` };
248
+ },
249
+ onComplete: async () => {}
250
+ }
251
+ }
252
+ ]
253
+ }).routes
254
+ )
255
+ .use(
256
+ createVoiceOperationsRecordRoutes({
257
+ audit: runtime.audit,
258
+ htmlPath: '/voice-operations/:sessionId',
259
+ path: '/api/voice-operations/:sessionId',
260
+ store: runtime.traces
261
+ })
262
+ )
263
+ .use(
264
+ createVoiceProductionReadinessRoutes({
265
+ ...createVoiceReadinessProfile('phone-agent', {
266
+ explain: true
267
+ }),
268
+ links: {
269
+ operationsRecords: '/voice-operations/:sessionId'
270
+ },
271
+ path: '/api/production-readiness',
272
+ htmlPath: '/production-readiness',
273
+ store: runtime.traces
274
+ })
275
+ );
276
+ ```
277
+
278
+ Open `/api/voice/phone/setup?format=html`, copy the reported Twilio URLs into the carrier dashboard, run one smoke call, then inspect:
279
+
280
+ - `/api/carriers?format=html`: carrier setup matrix.
281
+ - `/voice/phone/smoke-contract?sessionId=...`: trace-backed phone smoke proof.
282
+ - `/voice-operations/:sessionId`: call-log/debug record.
283
+ - `/production-readiness`: phone-agent readiness gate.
284
+
68
285
  ## Browser Voice Agent
69
286
 
70
287
  ```ts
@@ -208,6 +425,8 @@ Recommended proof routes:
208
425
  - `/api/voice/ops-status/html`: HTML status card for quick internal review.
209
426
  - `/demo-ready`: customer-facing demo readiness checklist.
210
427
  - `/production-readiness`: production gate summary.
428
+ - `/voice-operations/:sessionId`: default call-log/debug record for one problematic session.
429
+ - `/voice-operations/:sessionId/incident.md`: copyable incident handoff for support and engineering.
211
430
  - `/audit/deliveries`: audit sink export queue and failed delivery details.
212
431
  - `/voice/phone/smoke-contract`: trace-backed phone-agent production smoke proof.
213
432
  - `/traces`: per-session trace timelines.
@@ -906,6 +1125,119 @@ For HTML or HTMX pages:
906
1125
  </script>
907
1126
  ```
908
1127
 
1128
+ ## Live Operator Workflows
1129
+
1130
+ Use live-ops primitives when an operator needs to intervene during an active session without taking voice orchestration out of your app. The supported actions are:
1131
+
1132
+ - `pause-assistant`: keep committing caller turns, but skip assistant generation.
1133
+ - `resume-assistant`: let automation continue.
1134
+ - `operator-takeover`: keep the session open while a human handles the caller.
1135
+ - `inject-instruction`: add an operator instruction to the next assistant turn.
1136
+ - `force-handoff`: mark the session for a specific handoff target.
1137
+ - `escalate`, `assign`, `tag`, and `create-task`: record operational intent and make it auditable.
1138
+
1139
+ Mount the live-ops control routes beside your voice route:
1140
+
1141
+ ```ts
1142
+ import {
1143
+ createVoiceLiveOpsRoutes,
1144
+ createVoiceMemoryLiveOpsControlStore,
1145
+ voice
1146
+ } from '@absolutejs/voice';
1147
+ import { deepgram } from '@absolutejs/voice-deepgram';
1148
+
1149
+ const liveOps = createVoiceMemoryLiveOpsControlStore();
1150
+
1151
+ app
1152
+ .use(
1153
+ createVoiceLiveOpsRoutes({
1154
+ audit: runtimeStorage.audit,
1155
+ store: liveOps,
1156
+ trace: runtimeStorage.traces
1157
+ })
1158
+ )
1159
+ .use(
1160
+ voice({
1161
+ path: '/voice',
1162
+ liveOps: {
1163
+ getControl: (sessionId) => liveOps.get(sessionId)
1164
+ },
1165
+ session: runtimeStorage.session,
1166
+ stt: deepgram({ apiKey: process.env.DEEPGRAM_API_KEY! }),
1167
+ async onTurn({ turn }) {
1168
+ return { assistantText: `I heard: ${turn.text}` };
1169
+ },
1170
+ onComplete: async () => {}
1171
+ })
1172
+ );
1173
+ ```
1174
+
1175
+ The default route accepts `POST /api/voice/live-ops/action`:
1176
+
1177
+ ```ts
1178
+ await fetch('/api/voice/live-ops/action', {
1179
+ body: JSON.stringify({
1180
+ action: 'pause-assistant',
1181
+ assignee: 'operator-123',
1182
+ detail: 'Caller is upset; pause automation while support reviews.',
1183
+ sessionId: 'session-123',
1184
+ tag: 'priority-support'
1185
+ }),
1186
+ headers: { 'content-type': 'application/json' },
1187
+ method: 'POST'
1188
+ });
1189
+ ```
1190
+
1191
+ Every action updates the control store and can write both `operator.action` audit events and `operator.action` trace events. The voice runtime checks the control state before assistant generation. When `assistantPaused` or `operatorTakeover` is active, it commits the user transcript, records a skipped-turn trace, and does not call the assistant. When `injectedInstruction` is present, the next assistant turn receives that instruction.
1192
+
1193
+ The safe operator runbook is:
1194
+
1195
+ 1. Open the session's operations record or trace timeline before intervening.
1196
+ 2. Use `pause-assistant` when the bot should stop responding but the transcript should continue.
1197
+ 3. Use `inject-instruction` when the bot should continue with human guidance, such as "apologize and offer transfer."
1198
+ 4. Use `operator-takeover` when a human is now handling the caller and automation must stay silent.
1199
+ 5. Use `force-handoff` or `escalate` when the session needs a specialist, supervisor, or external queue.
1200
+ 6. Use `resume-assistant` only after the operator has verified the session state and any handoff context.
1201
+ 7. Review `/api/voice/live-ops/control/:sessionId`, `/voice-operations/:sessionId`, `/api/voice/ops-actions/history`, or `/audit` when you need proof of who intervened and why.
1202
+
1203
+ Framework and HTML clients can run the same actions without a custom dashboard:
1204
+
1205
+ ```tsx
1206
+ import { useVoiceLiveOps } from '@absolutejs/voice/react';
1207
+
1208
+ export function LiveOperatorPanel({ sessionId }: { sessionId: string }) {
1209
+ const liveOps = useVoiceLiveOps();
1210
+
1211
+ return (
1212
+ <button
1213
+ onClick={() =>
1214
+ liveOps.run({
1215
+ action: 'operator-takeover',
1216
+ assignee: 'operator-123',
1217
+ detail: 'Human support took over the call.',
1218
+ sessionId,
1219
+ tag: 'human-takeover'
1220
+ })
1221
+ }
1222
+ >
1223
+ Take over
1224
+ </button>
1225
+ );
1226
+ }
1227
+ ```
1228
+
1229
+ For HTML or HTMX pages:
1230
+
1231
+ ```html
1232
+ <absolute-voice-live-ops session-id="session-123"></absolute-voice-live-ops>
1233
+ <script type="module">
1234
+ import { defineVoiceLiveOpsElement } from '@absolutejs/voice/client';
1235
+ defineVoiceLiveOpsElement();
1236
+ </script>
1237
+ ```
1238
+
1239
+ Live-ops is intentionally a primitive layer: the package records controls, audit evidence, and trace evidence, while your app decides which operators are allowed to run actions and how those controls appear in the product UI.
1240
+
909
1241
  ## Voice Assistants
910
1242
 
911
1243
  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(...)`.
@@ -1092,6 +1424,44 @@ Pass `contextPolicy` when a specialist should receive a controlled context windo
1092
1424
 
1093
1425
  Each specialist owns its own `tools`, so tool permissions stay explicit per agent. For example, support can have `lookup_order`, billing can have `refund_invoice`, and scheduling can have `book_appointment`. The squad only routes; it does not give every specialist every tool by default.
1094
1426
 
1427
+ Make the current specialist visible in your UI by mounting trace timelines and using the squad status primitives. They derive current specialist state from `agent.handoff`, `agent.context`, `agent.model`, and `agent.result` traces, so the UI stays tied to the same proof source used by readiness and operations records.
1428
+
1429
+ ```tsx
1430
+ import { VoiceAgentSquadStatus } from '@absolutejs/voice/react';
1431
+
1432
+ export function SpecialistBadge({ sessionId }: { sessionId: string }) {
1433
+ return (
1434
+ <VoiceAgentSquadStatus
1435
+ path="/api/voice-traces"
1436
+ sessionId={sessionId}
1437
+ title="Current specialist"
1438
+ />
1439
+ );
1440
+ }
1441
+ ```
1442
+
1443
+ Framework equivalents are available without a dashboard:
1444
+
1445
+ ```ts
1446
+ import { useVoiceAgentSquadStatus } from '@absolutejs/voice/vue';
1447
+ import { createVoiceAgentSquadStatus } from '@absolutejs/voice/svelte';
1448
+ import { VoiceAgentSquadStatusService } from '@absolutejs/voice/angular';
1449
+ ```
1450
+
1451
+ For HTML or HTMX pages:
1452
+
1453
+ ```html
1454
+ <absolute-voice-agent-squad-status
1455
+ path="/api/voice-traces"
1456
+ session-id="session-123"
1457
+ title="Current specialist"
1458
+ ></absolute-voice-agent-squad-status>
1459
+ <script type="module">
1460
+ import { defineVoiceAgentSquadStatusElement } from '@absolutejs/voice/client';
1461
+ defineVoiceAgentSquadStatusElement();
1462
+ </script>
1463
+ ```
1464
+
1095
1465
  Use `runVoiceAgentSquadContract(...)` in tests or readiness checks when you need proof that a specialist graph still routes correctly:
1096
1466
 
1097
1467
  ```ts
@@ -1380,6 +1750,10 @@ app.use(
1380
1750
 
1381
1751
  `createVoiceOperationsRecordRoutes(...)` links the call/session timeline, transcript, replay, provider decisions, tools, handoffs, audit, reviews, ops tasks, integration events, and sink delivery attempts into one debuggable object. Use `/voice-operations/:sessionId` as the first place to investigate failed calls, provider failures, handoff failures, slow turns, and campaign attempts. The same mount also exposes incident handoff Markdown at `/voice-operations/:sessionId/incident.md` and `/api/voice-operations/:sessionId/incident.md` for support tooling.
1382
1752
 
1753
+ Most proof surfaces can link to the same record by passing an operations-record URL template such as `/voice-operations/:sessionId`. Use that template anywhere a report emits session-level failures: production readiness, ops recovery, trace timelines, session lists, reviews, campaign attempts, eval reports, simulation-suite actions, tool-contract cases, and outcome-contract matched sessions. The goal is that no operator has to guess which trace, review, task, or delivery queue belongs to the failing call.
1754
+
1755
+ If a customer asks for "the call log," send the operations-record URL. If engineering needs reproducible context, send the incident Markdown URL. If a deploy gate fails, start at readiness or ops recovery and follow the linked operations record instead of searching storage manually.
1756
+
1383
1757
  Mount `createVoiceOpsRecoveryRoutes(...)` beside it when operators need one deploy-checkable recovery signal:
1384
1758
 
1385
1759
  ```ts
@@ -10,6 +10,7 @@ export { VoiceProviderContractsService } from './voice-provider-contracts.servic
10
10
  export { VoiceProviderStatusService } from './voice-provider-status.service';
11
11
  export { VoiceRoutingStatusService } from './voice-routing-status.service';
12
12
  export { VoiceTraceTimelineService } from './voice-trace-timeline.service';
13
+ export { VoiceAgentSquadStatusService } from './voice-agent-squad-status.service';
13
14
  export { VoiceTurnLatencyService } from './voice-turn-latency.service';
14
15
  export { VoiceTurnQualityService } from './voice-turn-quality.service';
15
16
  export { VoiceWorkflowStatusService } from './voice-workflow-status.service';