@absolutejs/voice 0.0.22-beta.330 → 0.0.22-beta.332

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.
@@ -4,7 +4,7 @@ export type VoiceCompetitiveCoverageLevel = 'covered' | 'intentional-gap' | 'mis
4
4
  export type VoiceCompetitiveDepthLevel = 'advantage' | 'covered' | 'intentional-gap' | 'lag' | 'parity';
5
5
  export type VoiceCompetitiveEvidence = {
6
6
  href?: string;
7
- kind?: 'docs' | 'example' | 'operations-record' | 'proof' | 'readiness' | 'route' | 'test';
7
+ kind?: 'docs' | 'example' | 'failure-replay' | 'operations-record' | 'proof' | 'readiness' | 'route' | 'test';
8
8
  name: string;
9
9
  required?: boolean;
10
10
  status?: 'fail' | 'pass' | 'warn';
package/dist/index.d.ts CHANGED
@@ -73,7 +73,7 @@ export { acknowledgeVoiceMonitorIssue, buildVoiceMonitorRunReport, createVoiceMe
73
73
  export { createVoiceReadinessProfile, recommendVoiceReadinessProfile } from './readinessProfiles';
74
74
  export { assertVoiceProviderContractMatrixEvidence, assertVoiceProviderStackEvidence, buildVoiceProviderContractMatrix, createVoiceProviderContractMatrixHTMLHandler, createVoiceProviderContractMatrixJSONHandler, createVoiceProviderContractMatrixPreset, createVoiceProviderContractMatrixRoutes, evaluateVoiceProviderContractMatrixEvidence, evaluateVoiceProviderStackEvidence, evaluateVoiceProviderStackGaps, renderVoiceProviderContractMatrixHTML, recommendVoiceProviderStack } from './providerStackRecommendations';
75
75
  export { buildVoiceOpsConsoleReport, createVoiceOpsConsoleRoutes, renderVoiceOpsConsoleHTML } from './opsConsoleRoutes';
76
- export { assertVoiceOperationsRecordGuardrails, assertVoiceOperationsRecordProviderRecovery, buildVoiceOperationsRecord, createVoiceOperationsRecordRoutes, evaluateVoiceOperationsRecordGuardrails, evaluateVoiceOperationsRecordProviderRecovery, renderVoiceOperationsRecordGuardrailMarkdown, renderVoiceOperationsRecordHTML, renderVoiceOperationsRecordIncidentMarkdown } from './operationsRecord';
76
+ export { assertVoiceOperationsRecordGuardrails, assertVoiceOperationsRecordProviderRecovery, buildVoiceFailureReplay, buildVoiceOperationsRecord, createVoiceOperationsRecordRoutes, evaluateVoiceOperationsRecordGuardrails, evaluateVoiceOperationsRecordProviderRecovery, renderVoiceFailureReplayMarkdown, renderVoiceOperationsRecordGuardrailMarkdown, renderVoiceOperationsRecordHTML, renderVoiceOperationsRecordIncidentMarkdown } from './operationsRecord';
77
77
  export { assertVoiceObservabilityExportDeliveryEvidence, assertVoiceObservabilityExportRecord, assertVoiceObservabilityExportReplayEvidence, buildVoiceObservabilityArtifactIndex, buildVoiceObservabilityExportDeliveryHistory, buildVoiceObservabilityExportReplayReport, buildVoiceObservabilityExport, assertVoiceObservabilityExportSchema, createVoiceObservabilityExportSchema, createVoiceFileObservabilityExportDeliveryReceiptStore, createVoiceMemoryObservabilityExportDeliveryReceiptStore, createVoiceObservabilityExportRoutes, createVoiceObservabilityExportReplayRoutes, deliverVoiceObservabilityExport, evaluateVoiceObservabilityExportDeliveryEvidence, evaluateVoiceObservabilityExportReplayEvidence, loadVoiceObservabilityExportReplaySource, replayVoiceObservabilityExport, renderVoiceObservabilityExportReplayHTML, renderVoiceObservabilityExportMarkdown, validateVoiceObservabilityExportRecord, voiceObservabilityExportSchemaId, voiceObservabilityExportSchemaVersion } from './observabilityExport';
78
78
  export { buildVoiceOpsRecoveryReadinessCheck, buildVoiceOpsRecoveryReport, createVoiceOpsRecoveryRoutes, renderVoiceOpsRecoveryHTML, renderVoiceOpsRecoveryMarkdown } from './opsRecovery';
79
79
  export { buildVoiceIncidentBundle, createStoredVoiceIncidentBundleArtifact, createVoiceIncidentBundleRoutes, createVoiceMemoryIncidentBundleStore, pruneVoiceIncidentBundleArtifacts, saveVoiceIncidentBundleArtifact } from './incidentBundle';
@@ -142,7 +142,7 @@ export type { VoiceProductionReadinessAction, VoiceProductionReadinessAuditOptio
142
142
  export type { VoiceMonitorDefinition, VoiceMonitorEvaluation, VoiceMonitorEvaluationInput, VoiceMonitorIssue, VoiceMonitorIssueStatus, VoiceMonitorIssueStore, VoiceMonitorNotifier, VoiceMonitorNotifierDeliveryInput, VoiceMonitorNotifierDeliveryOptions, VoiceMonitorNotifierDeliveryReceipt, VoiceMonitorNotifierDeliveryReceiptStore, VoiceMonitorNotifierDeliveryReport, VoiceMonitorNotifierDeliveryResult, VoiceMonitorRoutesOptions, VoiceMonitorRun, VoiceMonitorRunOptions, VoiceMonitorRunReport, VoiceMonitorRunner, VoiceMonitorRunnerOptions, VoiceMonitorRunnerRoutesOptions, VoiceMonitorRunnerTickResult, VoiceMonitorSeverity, VoiceMonitorStatus, VoiceMonitorWebhookNotifierOptions } from './voiceMonitoring';
143
143
  export type { VoiceReadinessProfileName, VoiceReadinessProfileOptions, VoiceReadinessProfileRecommendation, VoiceReadinessProfileRecommendationScore, VoiceReadinessProfileRoutesOptions } from './readinessProfiles';
144
144
  export type { VoiceProviderStackChoice, VoiceProviderStackCapabilities, VoiceProviderStackCapabilityGap, VoiceProviderStackCapabilityGapInput, VoiceProviderStackCapabilityGapReport, VoiceProviderContractCheck, VoiceProviderContractCheckStatus, VoiceProviderContractDefinition, VoiceProviderContractMatrixAssertionInput, VoiceProviderContractMatrixAssertionReport, VoiceProviderContractMatrixHandlerOptions, VoiceProviderContractMatrixHTMLHandlerOptions, VoiceProviderContractMatrixInput, VoiceProviderContractMatrixPresetOptions, VoiceProviderContractMatrixReport, VoiceProviderContractMatrixRoutesOptions, VoiceProviderContractMatrixRow, VoiceProviderStackAssertionInput, VoiceProviderStackAssertionReport, VoiceProviderStackInput, VoiceProviderStackKind, VoiceProviderStackRecommendation } from './providerStackRecommendations';
145
- export type { VoiceOperationsRecord, VoiceOperationsRecordAgentHandoff, VoiceOperationsRecordAuditSummary, VoiceOperationsRecordGuardrailAssertionInput, VoiceOperationsRecordGuardrailAssertionReport, VoiceOperationsRecordGuardrailDecision, VoiceOperationsRecordGuardrailFinding, VoiceOperationsRecordGuardrailSummary, VoiceOperationsRecordIntegrationEventSummary, VoiceOperationsRecordOptions, VoiceOperationsRecordOutcome, VoiceOperationsRecordProviderDecision, VoiceOperationsRecordProviderDecisionRecoveryStatus, VoiceOperationsRecordProviderDecisionSummary, VoiceOperationsRecordProviderRecoveryAssertionInput, VoiceOperationsRecordProviderRecoveryAssertionReport, VoiceOperationsRecordReviewSummary, VoiceOperationsRecordRoutesOptions, VoiceOperationsRecordStatus, VoiceOperationsRecordTaskSummary, VoiceOperationsRecordTelephonyMediaEvent, VoiceOperationsRecordTelephonyMediaSummary, VoiceOperationsRecordTranscriptTurn, VoiceOperationsRecordTool } from './operationsRecord';
145
+ export type { VoiceFailureReplayMediaStep, VoiceFailureReplayOptions, VoiceFailureReplayProviderStep, VoiceFailureReplayReport, VoiceFailureReplayStatus, VoiceFailureReplayTurn, VoiceOperationsRecord, VoiceOperationsRecordAgentHandoff, VoiceOperationsRecordAuditSummary, VoiceOperationsRecordGuardrailAssertionInput, VoiceOperationsRecordGuardrailAssertionReport, VoiceOperationsRecordGuardrailDecision, VoiceOperationsRecordGuardrailFinding, VoiceOperationsRecordGuardrailSummary, VoiceOperationsRecordIntegrationEventSummary, VoiceOperationsRecordOptions, VoiceOperationsRecordOutcome, VoiceOperationsRecordProviderDecision, VoiceOperationsRecordProviderDecisionRecoveryStatus, VoiceOperationsRecordProviderDecisionSummary, VoiceOperationsRecordProviderRecoveryAssertionInput, VoiceOperationsRecordProviderRecoveryAssertionReport, VoiceOperationsRecordReviewSummary, VoiceOperationsRecordRoutesOptions, VoiceOperationsRecordStatus, VoiceOperationsRecordTaskSummary, VoiceOperationsRecordTelephonyMediaEvent, VoiceOperationsRecordTelephonyMediaSummary, VoiceOperationsRecordTranscriptTurn, VoiceOperationsRecordTool } from './operationsRecord';
146
146
  export type { VoiceObservabilityExportArtifact, VoiceObservabilityExportArtifactChecksum, VoiceObservabilityExportArtifactFreshness, VoiceObservabilityExportArtifactIndex, VoiceObservabilityExportArtifactIndexItem, VoiceObservabilityExportArtifactKind, VoiceObservabilityExportDeliveryAssertionInput, VoiceObservabilityExportDeliveryAssertionReport, VoiceObservabilityExportDeliverySummary, VoiceObservabilityExportDeliveryDestination, VoiceObservabilityExportDeliveryDestinationResult, VoiceObservabilityExportDeliveryHistory, VoiceObservabilityExportDeliveryOptions, VoiceObservabilityExportDeliveryReceipt, VoiceObservabilityExportDeliveryReceiptStore, VoiceObservabilityExportDeliveryReport, VoiceObservabilityExportEnvelope, VoiceObservabilityExportIssue, VoiceObservabilityExportIssueCode, VoiceObservabilityExportOptions, VoiceObservabilityExportIngestedRecordKind, VoiceObservabilityExportRedactionSummary, VoiceObservabilityExportRecordValidationOptions, VoiceObservabilityExportReplayIssue, VoiceObservabilityExportReplayIssueCode, VoiceObservabilityExportReplayAssertionInput, VoiceObservabilityExportReplayAssertionReport, VoiceObservabilityExportReplayRecords, VoiceObservabilityExportReplayReport, VoiceObservabilityExportReplayRoutesOptions, VoiceObservabilityExportReplaySource, VoiceObservabilityExportReport, VoiceObservabilityExportRoutesOptions, VoiceObservabilityExportSchema, VoiceObservabilityExportStatus, VoiceObservabilityExportValidationIssue, VoiceObservabilityExportValidationResult } from './observabilityExport';
147
147
  export type { VoiceOpsRecoveryFailedSession, VoiceOpsRecoveryInterventionSummary, VoiceOpsRecoveryIssue, VoiceOpsRecoveryIssueCode, VoiceOpsRecoveryLinks, VoiceOpsRecoveryProviderSummary, VoiceOpsRecoveryReport, VoiceOpsRecoveryReportOptions, VoiceOpsRecoveryRoutesOptions, VoiceOpsRecoveryStatus } from './opsRecovery';
148
148
  export type { StoredVoiceIncidentBundleArtifact, VoiceIncidentBundle, VoiceIncidentBundleArtifactOptions, VoiceIncidentBundleFormat, VoiceIncidentBundleOptions, VoiceIncidentBundleRetentionOptions, VoiceIncidentBundleRetentionReport, VoiceIncidentBundleRoutesOptions, VoiceIncidentBundleStore, VoiceIncidentBundleStoreFilter, VoiceIncidentBundleSummary } from './incidentBundle';
package/dist/index.js CHANGED
@@ -27838,6 +27838,7 @@ var pushMissingValuesIssue = (input) => {
27838
27838
  }
27839
27839
  };
27840
27840
  var resolveRoutePath = (path, sessionId) => path.replace(":sessionId", encodeURIComponent(sessionId));
27841
+ var resolveOperationsRecordHref = (href, record) => typeof href === "function" ? href({ record, sessionId: record.sessionId }) : href;
27841
27842
  var toHandoff = (event) => ({
27842
27843
  at: event.at,
27843
27844
  fromAgentId: getString17(event.payload.fromAgentId),
@@ -28252,6 +28253,150 @@ var assertVoiceOperationsRecordProviderRecovery = (record, input = {}) => {
28252
28253
  }
28253
28254
  return report;
28254
28255
  };
28256
+ var getAssistantRepliesForTurn = (record, turnId) => turnId ? record.transcript.find((turn) => turn.id === turnId)?.assistantReplies ?? [] : [];
28257
+ var mediaIssueForStep = (step) => {
28258
+ const event = step.event.toLowerCase();
28259
+ if (event === "error") {
28260
+ return "Carrier media stream emitted an error.";
28261
+ }
28262
+ if (event === "media" && step.audioBytes <= 0) {
28263
+ return "Carrier media packet had no audio bytes.";
28264
+ }
28265
+ return;
28266
+ };
28267
+ var buildVoiceFailureReplay = (record, options = {}) => {
28268
+ const providerSteps = record.providerDecisions.filter((decision) => ["degraded", "error", "fallback", "selected", "success"].includes(decision.status ?? "")).map((decision) => ({
28269
+ at: decision.at,
28270
+ elapsedMs: decision.elapsedMs,
28271
+ error: decision.error,
28272
+ fallbackProvider: decision.fallbackProvider,
28273
+ kind: decision.kind,
28274
+ provider: decision.provider,
28275
+ reason: decision.reason,
28276
+ selectedProvider: decision.selectedProvider,
28277
+ status: decision.status,
28278
+ surface: decision.surface,
28279
+ turnId: decision.turnId,
28280
+ userHeard: getAssistantRepliesForTurn(record, decision.turnId)
28281
+ }));
28282
+ const mediaSteps = record.telephonyMedia.events.map((event) => {
28283
+ const step = {
28284
+ at: event.at,
28285
+ audioBytes: event.audioBytes,
28286
+ carrier: event.carrier,
28287
+ direction: event.direction,
28288
+ event: event.event,
28289
+ streamId: event.streamId
28290
+ };
28291
+ return {
28292
+ ...step,
28293
+ issue: mediaIssueForStep(step)
28294
+ };
28295
+ });
28296
+ const providerIssues = providerSteps.filter((step) => step.status === "error" || step.status === "fallback" || step.status === "degraded").map((step) => {
28297
+ const provider = step.provider ?? step.selectedProvider ?? "provider";
28298
+ const recovery = step.status === "fallback" ? `recovered through ${step.fallbackProvider ?? step.selectedProvider ?? "fallback"}` : step.status === "degraded" ? `degraded to ${step.fallbackProvider ?? step.selectedProvider ?? "fallback"}` : "failed before recovery";
28299
+ return `${provider} ${recovery}${step.reason ? `: ${step.reason}` : ""}`;
28300
+ });
28301
+ const mediaIssues = [
28302
+ ...mediaSteps.map((step) => step.issue).filter((issue) => typeof issue === "string"),
28303
+ record.telephonyMedia.total > 0 && record.telephonyMedia.inbound === 0 ? "Carrier stream has no inbound media evidence." : undefined,
28304
+ record.telephonyMedia.total > 0 && record.telephonyMedia.outbound === 0 ? "Carrier stream has no outbound assistant media/control evidence." : undefined
28305
+ ].filter((issue) => typeof issue === "string");
28306
+ const userHeard = [
28307
+ ...new Set(record.transcript.flatMap((turn) => turn.assistantReplies))
28308
+ ];
28309
+ const status = record.providerDecisionSummary.errors > 0 || record.telephonyMedia.errors > 0 ? "failed" : record.providerDecisionSummary.degraded > 0 ? "degraded" : record.providerDecisionSummary.fallbacks > 0 || mediaIssues.length > 0 ? "recovered" : "healthy";
28310
+ const turns = record.transcript.map((turn) => ({
28311
+ ...turn,
28312
+ media: mediaSteps.filter((step) => record.traceEvents.some((event) => event.turnId === turn.id && event.type === "client.telephony_media" && event.at === step.at)),
28313
+ providers: providerSteps.filter((step) => step.turnId === turn.id)
28314
+ }));
28315
+ const issues = [...providerIssues, ...mediaIssues];
28316
+ const reportBase = {
28317
+ media: {
28318
+ audioBytes: record.telephonyMedia.audioBytes,
28319
+ clears: record.telephonyMedia.clears,
28320
+ errors: record.telephonyMedia.errors,
28321
+ issues: mediaIssues,
28322
+ steps: mediaSteps,
28323
+ total: record.telephonyMedia.total
28324
+ },
28325
+ ok: status === "healthy" || status === "recovered",
28326
+ operationsRecordHref: resolveOperationsRecordHref(options.operationsRecordHref, record),
28327
+ providers: {
28328
+ degraded: record.providerDecisionSummary.degraded,
28329
+ errors: record.providerDecisionSummary.errors,
28330
+ fallbacks: record.providerDecisionSummary.fallbacks,
28331
+ recoveryStatus: record.providerDecisionSummary.recoveryStatus,
28332
+ selected: record.providerDecisionSummary.selected,
28333
+ steps: providerSteps,
28334
+ total: record.providerDecisionSummary.total
28335
+ },
28336
+ sessionId: record.sessionId,
28337
+ status,
28338
+ summary: {
28339
+ callDurationMs: record.summary.callDurationMs,
28340
+ issues,
28341
+ userHeard
28342
+ },
28343
+ turns
28344
+ };
28345
+ return {
28346
+ ...reportBase,
28347
+ incidentMarkdown: renderVoiceFailureReplayMarkdown(reportBase)
28348
+ };
28349
+ };
28350
+ var renderVoiceFailureReplayMarkdown = (report) => {
28351
+ const heard = report.summary.userHeard.length ? report.summary.userHeard.map((text) => `- ${text}`).join(`
28352
+ `) : "- none recorded";
28353
+ const providerSteps = report.providers.steps.length ? report.providers.steps.slice(0, 8).map((step) => {
28354
+ const provider = step.provider ?? step.selectedProvider ?? "provider";
28355
+ const parts = [
28356
+ step.status ? `status=${step.status}` : undefined,
28357
+ step.selectedProvider ? `selected=${step.selectedProvider}` : undefined,
28358
+ step.fallbackProvider ? `fallback=${step.fallbackProvider}` : undefined,
28359
+ step.elapsedMs !== undefined ? `elapsed=${step.elapsedMs}ms` : undefined,
28360
+ step.reason
28361
+ ].filter((part) => typeof part === "string");
28362
+ return `- ${provider}: ${parts.join("; ")}`;
28363
+ }).join(`
28364
+ `) : "- none recorded";
28365
+ const mediaSteps = report.media.steps.length ? report.media.steps.slice(0, 8).map((step) => {
28366
+ const parts = [
28367
+ step.carrier,
28368
+ step.event,
28369
+ step.direction,
28370
+ step.streamId ? `stream=${step.streamId}` : undefined,
28371
+ `audioBytes=${String(step.audioBytes)}`,
28372
+ step.issue
28373
+ ].filter((part) => typeof part === "string");
28374
+ return `- ${parts.join("; ")}`;
28375
+ }).join(`
28376
+ `) : "- none recorded";
28377
+ const issues = report.summary.issues.length ? report.summary.issues.map((issue) => `- ${issue}`).join(`
28378
+ `) : "- none";
28379
+ return [
28380
+ `# Voice Failure Replay: ${report.sessionId}`,
28381
+ "",
28382
+ `Status: ${report.status}`,
28383
+ `Operations record: ${report.operationsRecordHref ?? "not linked"}`,
28384
+ `Duration: ${formatMs5(report.summary.callDurationMs)}`,
28385
+ "",
28386
+ "## What Failed Or Recovered",
28387
+ issues,
28388
+ "",
28389
+ "## Provider Path",
28390
+ providerSteps,
28391
+ "",
28392
+ "## Media Path",
28393
+ mediaSteps,
28394
+ "",
28395
+ "## What The User Heard",
28396
+ heard
28397
+ ].join(`
28398
+ `);
28399
+ };
28255
28400
  var escapeHtml48 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
28256
28401
  var formatMs5 = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
28257
28402
  var outcomeLabels = (outcome) => [
@@ -35656,7 +35801,7 @@ var createFakeTTSAdapter = (chunkCount, chunkDelayMs) => ({
35656
35801
  var defaultOperationsRecordHref = ({
35657
35802
  sessionId
35658
35803
  }) => `/voice-operations/${encodeURIComponent(sessionId)}`;
35659
- var resolveOperationsRecordHref = (href, input) => typeof href === "function" ? href(input) : href === undefined ? defaultOperationsRecordHref(input) : href;
35804
+ var resolveOperationsRecordHref2 = (href, input) => typeof href === "function" ? href(input) : href === undefined ? defaultOperationsRecordHref(input) : href;
35660
35805
  var runVoiceTelephonyMediaOperationsSmoke = async (options = {}) => {
35661
35806
  const timeoutMs = options.timeoutMs ?? 5000;
35662
35807
  const sessionId = options.sessionId ?? `telephony-media-ops-${Date.now()}`;
@@ -35744,7 +35889,7 @@ var runVoiceTelephonyMediaOperationsSmoke = async (options = {}) => {
35744
35889
  issues,
35745
35890
  ok: issues.length === 0,
35746
35891
  operationsRecord,
35747
- operationsRecordHref: resolveOperationsRecordHref(options.operationsRecordHref, { sessionId, streamSid }),
35892
+ operationsRecordHref: resolveOperationsRecordHref2(options.operationsRecordHref, { sessionId, streamSid }),
35748
35893
  sentEvents: sentEvents.map((message) => message.event).filter((event) => typeof event === "string"),
35749
35894
  sessionId,
35750
35895
  streamSid,
@@ -36068,6 +36213,7 @@ export {
36068
36213
  renderVoiceLatencySLOMarkdown,
36069
36214
  renderVoiceHandoffHealthHTML,
36070
36215
  renderVoiceGuardrailMarkdown,
36216
+ renderVoiceFailureReplayMarkdown,
36071
36217
  renderVoiceEvalHTML,
36072
36218
  renderVoiceEvalBaselineHTML,
36073
36219
  renderVoiceDemoReadyHTML,
@@ -36507,6 +36653,7 @@ export {
36507
36653
  buildVoiceLatencySLOGate,
36508
36654
  buildVoiceIncidentBundle,
36509
36655
  buildVoiceGuardrailReport,
36656
+ buildVoiceFailureReplay,
36510
36657
  buildVoiceDiagnosticsMarkdown,
36511
36658
  buildVoiceDemoReadyReport,
36512
36659
  buildVoiceDeliverySinkReport,
@@ -217,6 +217,75 @@ export type VoiceOperationsRecord = {
217
217
  traceEvents: StoredVoiceTraceEvent[];
218
218
  transcript: VoiceOperationsRecordTranscriptTurn[];
219
219
  };
220
+ export type VoiceFailureReplayStatus = 'degraded' | 'failed' | 'healthy' | 'recovered';
221
+ export type VoiceFailureReplayProviderStep = {
222
+ at: number;
223
+ elapsedMs?: number;
224
+ error?: string;
225
+ fallbackProvider?: string;
226
+ kind?: string;
227
+ provider?: string;
228
+ reason?: string;
229
+ selectedProvider?: string;
230
+ status?: string;
231
+ surface?: string;
232
+ turnId?: string;
233
+ userHeard: string[];
234
+ };
235
+ export type VoiceFailureReplayMediaStep = {
236
+ at: number;
237
+ audioBytes: number;
238
+ carrier?: string;
239
+ direction?: string;
240
+ event: string;
241
+ issue?: string;
242
+ streamId?: string;
243
+ };
244
+ export type VoiceFailureReplayTurn = {
245
+ assistantReplies: string[];
246
+ committedText?: string;
247
+ errors: string[];
248
+ id: string;
249
+ media: VoiceFailureReplayMediaStep[];
250
+ providers: VoiceFailureReplayProviderStep[];
251
+ transcripts: string[];
252
+ };
253
+ export type VoiceFailureReplayReport = {
254
+ incidentMarkdown: string;
255
+ media: {
256
+ audioBytes: number;
257
+ clears: number;
258
+ errors: number;
259
+ issues: string[];
260
+ steps: VoiceFailureReplayMediaStep[];
261
+ total: number;
262
+ };
263
+ ok: boolean;
264
+ operationsRecordHref?: string;
265
+ providers: {
266
+ degraded: number;
267
+ errors: number;
268
+ fallbacks: number;
269
+ recoveryStatus: VoiceOperationsRecordProviderDecisionRecoveryStatus;
270
+ selected: number;
271
+ steps: VoiceFailureReplayProviderStep[];
272
+ total: number;
273
+ };
274
+ sessionId: string;
275
+ status: VoiceFailureReplayStatus;
276
+ summary: {
277
+ callDurationMs?: number;
278
+ issues: string[];
279
+ userHeard: string[];
280
+ };
281
+ turns: VoiceFailureReplayTurn[];
282
+ };
283
+ export type VoiceFailureReplayOptions = {
284
+ operationsRecordHref?: string | ((input: {
285
+ record: VoiceOperationsRecord;
286
+ sessionId: string;
287
+ }) => string);
288
+ };
220
289
  export type VoiceOperationsRecordOptions = {
221
290
  audit?: VoiceAuditEventStore;
222
291
  evaluation?: VoiceTraceEvaluationOptions;
@@ -244,6 +313,8 @@ export declare const evaluateVoiceOperationsRecordGuardrails: (record: VoiceOper
244
313
  export declare const assertVoiceOperationsRecordGuardrails: (record: VoiceOperationsRecord, input?: VoiceOperationsRecordGuardrailAssertionInput) => VoiceOperationsRecordGuardrailAssertionReport;
245
314
  export declare const evaluateVoiceOperationsRecordProviderRecovery: (record: VoiceOperationsRecord, input?: VoiceOperationsRecordProviderRecoveryAssertionInput) => VoiceOperationsRecordProviderRecoveryAssertionReport;
246
315
  export declare const assertVoiceOperationsRecordProviderRecovery: (record: VoiceOperationsRecord, input?: VoiceOperationsRecordProviderRecoveryAssertionInput) => VoiceOperationsRecordProviderRecoveryAssertionReport;
316
+ export declare const buildVoiceFailureReplay: (record: VoiceOperationsRecord, options?: VoiceFailureReplayOptions) => VoiceFailureReplayReport;
317
+ export declare const renderVoiceFailureReplayMarkdown: (report: Omit<VoiceFailureReplayReport, "incidentMarkdown"> | VoiceFailureReplayReport) => string;
247
318
  export declare const renderVoiceOperationsRecordIncidentMarkdown: (record: VoiceOperationsRecord) => string;
248
319
  export declare const renderVoiceOperationsRecordGuardrailMarkdown: (record: VoiceOperationsRecord) => string;
249
320
  export declare const renderVoiceOperationsRecordHTML: (record: VoiceOperationsRecord, options?: {
@@ -10780,6 +10780,7 @@ var pushMissingValuesIssue = (input) => {
10780
10780
  }
10781
10781
  };
10782
10782
  var resolveRoutePath = (path, sessionId) => path.replace(":sessionId", encodeURIComponent(sessionId));
10783
+ var resolveOperationsRecordHref = (href, record) => typeof href === "function" ? href({ record, sessionId: record.sessionId }) : href;
10783
10784
  var toHandoff = (event) => ({
10784
10785
  at: event.at,
10785
10786
  fromAgentId: getString4(event.payload.fromAgentId),
@@ -11194,6 +11195,150 @@ var assertVoiceOperationsRecordProviderRecovery = (record, input = {}) => {
11194
11195
  }
11195
11196
  return report;
11196
11197
  };
11198
+ var getAssistantRepliesForTurn = (record, turnId) => turnId ? record.transcript.find((turn) => turn.id === turnId)?.assistantReplies ?? [] : [];
11199
+ var mediaIssueForStep = (step) => {
11200
+ const event = step.event.toLowerCase();
11201
+ if (event === "error") {
11202
+ return "Carrier media stream emitted an error.";
11203
+ }
11204
+ if (event === "media" && step.audioBytes <= 0) {
11205
+ return "Carrier media packet had no audio bytes.";
11206
+ }
11207
+ return;
11208
+ };
11209
+ var buildVoiceFailureReplay = (record, options = {}) => {
11210
+ const providerSteps = record.providerDecisions.filter((decision) => ["degraded", "error", "fallback", "selected", "success"].includes(decision.status ?? "")).map((decision) => ({
11211
+ at: decision.at,
11212
+ elapsedMs: decision.elapsedMs,
11213
+ error: decision.error,
11214
+ fallbackProvider: decision.fallbackProvider,
11215
+ kind: decision.kind,
11216
+ provider: decision.provider,
11217
+ reason: decision.reason,
11218
+ selectedProvider: decision.selectedProvider,
11219
+ status: decision.status,
11220
+ surface: decision.surface,
11221
+ turnId: decision.turnId,
11222
+ userHeard: getAssistantRepliesForTurn(record, decision.turnId)
11223
+ }));
11224
+ const mediaSteps = record.telephonyMedia.events.map((event) => {
11225
+ const step = {
11226
+ at: event.at,
11227
+ audioBytes: event.audioBytes,
11228
+ carrier: event.carrier,
11229
+ direction: event.direction,
11230
+ event: event.event,
11231
+ streamId: event.streamId
11232
+ };
11233
+ return {
11234
+ ...step,
11235
+ issue: mediaIssueForStep(step)
11236
+ };
11237
+ });
11238
+ const providerIssues = providerSteps.filter((step) => step.status === "error" || step.status === "fallback" || step.status === "degraded").map((step) => {
11239
+ const provider = step.provider ?? step.selectedProvider ?? "provider";
11240
+ const recovery = step.status === "fallback" ? `recovered through ${step.fallbackProvider ?? step.selectedProvider ?? "fallback"}` : step.status === "degraded" ? `degraded to ${step.fallbackProvider ?? step.selectedProvider ?? "fallback"}` : "failed before recovery";
11241
+ return `${provider} ${recovery}${step.reason ? `: ${step.reason}` : ""}`;
11242
+ });
11243
+ const mediaIssues = [
11244
+ ...mediaSteps.map((step) => step.issue).filter((issue) => typeof issue === "string"),
11245
+ record.telephonyMedia.total > 0 && record.telephonyMedia.inbound === 0 ? "Carrier stream has no inbound media evidence." : undefined,
11246
+ record.telephonyMedia.total > 0 && record.telephonyMedia.outbound === 0 ? "Carrier stream has no outbound assistant media/control evidence." : undefined
11247
+ ].filter((issue) => typeof issue === "string");
11248
+ const userHeard = [
11249
+ ...new Set(record.transcript.flatMap((turn) => turn.assistantReplies))
11250
+ ];
11251
+ const status = record.providerDecisionSummary.errors > 0 || record.telephonyMedia.errors > 0 ? "failed" : record.providerDecisionSummary.degraded > 0 ? "degraded" : record.providerDecisionSummary.fallbacks > 0 || mediaIssues.length > 0 ? "recovered" : "healthy";
11252
+ const turns = record.transcript.map((turn) => ({
11253
+ ...turn,
11254
+ media: mediaSteps.filter((step) => record.traceEvents.some((event) => event.turnId === turn.id && event.type === "client.telephony_media" && event.at === step.at)),
11255
+ providers: providerSteps.filter((step) => step.turnId === turn.id)
11256
+ }));
11257
+ const issues = [...providerIssues, ...mediaIssues];
11258
+ const reportBase = {
11259
+ media: {
11260
+ audioBytes: record.telephonyMedia.audioBytes,
11261
+ clears: record.telephonyMedia.clears,
11262
+ errors: record.telephonyMedia.errors,
11263
+ issues: mediaIssues,
11264
+ steps: mediaSteps,
11265
+ total: record.telephonyMedia.total
11266
+ },
11267
+ ok: status === "healthy" || status === "recovered",
11268
+ operationsRecordHref: resolveOperationsRecordHref(options.operationsRecordHref, record),
11269
+ providers: {
11270
+ degraded: record.providerDecisionSummary.degraded,
11271
+ errors: record.providerDecisionSummary.errors,
11272
+ fallbacks: record.providerDecisionSummary.fallbacks,
11273
+ recoveryStatus: record.providerDecisionSummary.recoveryStatus,
11274
+ selected: record.providerDecisionSummary.selected,
11275
+ steps: providerSteps,
11276
+ total: record.providerDecisionSummary.total
11277
+ },
11278
+ sessionId: record.sessionId,
11279
+ status,
11280
+ summary: {
11281
+ callDurationMs: record.summary.callDurationMs,
11282
+ issues,
11283
+ userHeard
11284
+ },
11285
+ turns
11286
+ };
11287
+ return {
11288
+ ...reportBase,
11289
+ incidentMarkdown: renderVoiceFailureReplayMarkdown(reportBase)
11290
+ };
11291
+ };
11292
+ var renderVoiceFailureReplayMarkdown = (report) => {
11293
+ const heard = report.summary.userHeard.length ? report.summary.userHeard.map((text) => `- ${text}`).join(`
11294
+ `) : "- none recorded";
11295
+ const providerSteps = report.providers.steps.length ? report.providers.steps.slice(0, 8).map((step) => {
11296
+ const provider = step.provider ?? step.selectedProvider ?? "provider";
11297
+ const parts = [
11298
+ step.status ? `status=${step.status}` : undefined,
11299
+ step.selectedProvider ? `selected=${step.selectedProvider}` : undefined,
11300
+ step.fallbackProvider ? `fallback=${step.fallbackProvider}` : undefined,
11301
+ step.elapsedMs !== undefined ? `elapsed=${step.elapsedMs}ms` : undefined,
11302
+ step.reason
11303
+ ].filter((part) => typeof part === "string");
11304
+ return `- ${provider}: ${parts.join("; ")}`;
11305
+ }).join(`
11306
+ `) : "- none recorded";
11307
+ const mediaSteps = report.media.steps.length ? report.media.steps.slice(0, 8).map((step) => {
11308
+ const parts = [
11309
+ step.carrier,
11310
+ step.event,
11311
+ step.direction,
11312
+ step.streamId ? `stream=${step.streamId}` : undefined,
11313
+ `audioBytes=${String(step.audioBytes)}`,
11314
+ step.issue
11315
+ ].filter((part) => typeof part === "string");
11316
+ return `- ${parts.join("; ")}`;
11317
+ }).join(`
11318
+ `) : "- none recorded";
11319
+ const issues = report.summary.issues.length ? report.summary.issues.map((issue) => `- ${issue}`).join(`
11320
+ `) : "- none";
11321
+ return [
11322
+ `# Voice Failure Replay: ${report.sessionId}`,
11323
+ "",
11324
+ `Status: ${report.status}`,
11325
+ `Operations record: ${report.operationsRecordHref ?? "not linked"}`,
11326
+ `Duration: ${formatMs2(report.summary.callDurationMs)}`,
11327
+ "",
11328
+ "## What Failed Or Recovered",
11329
+ issues,
11330
+ "",
11331
+ "## Provider Path",
11332
+ providerSteps,
11333
+ "",
11334
+ "## Media Path",
11335
+ mediaSteps,
11336
+ "",
11337
+ "## What The User Heard",
11338
+ heard
11339
+ ].join(`
11340
+ `);
11341
+ };
11197
11342
  var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11198
11343
  var formatMs2 = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
11199
11344
  var outcomeLabels = (outcome) => [
@@ -13130,7 +13275,7 @@ var createFakeTTSAdapter = (chunkCount, chunkDelayMs) => ({
13130
13275
  var defaultOperationsRecordHref = ({
13131
13276
  sessionId
13132
13277
  }) => `/voice-operations/${encodeURIComponent(sessionId)}`;
13133
- var resolveOperationsRecordHref = (href, input) => typeof href === "function" ? href(input) : href === undefined ? defaultOperationsRecordHref(input) : href;
13278
+ var resolveOperationsRecordHref2 = (href, input) => typeof href === "function" ? href(input) : href === undefined ? defaultOperationsRecordHref(input) : href;
13134
13279
  var runVoiceTelephonyMediaOperationsSmoke = async (options = {}) => {
13135
13280
  const timeoutMs = options.timeoutMs ?? 5000;
13136
13281
  const sessionId = options.sessionId ?? `telephony-media-ops-${Date.now()}`;
@@ -13218,7 +13363,7 @@ var runVoiceTelephonyMediaOperationsSmoke = async (options = {}) => {
13218
13363
  issues,
13219
13364
  ok: issues.length === 0,
13220
13365
  operationsRecord,
13221
- operationsRecordHref: resolveOperationsRecordHref(options.operationsRecordHref, { sessionId, streamSid }),
13366
+ operationsRecordHref: resolveOperationsRecordHref2(options.operationsRecordHref, { sessionId, streamSid }),
13222
13367
  sentEvents: sentEvents.map((message) => message.event).filter((event) => typeof event === "string"),
13223
13368
  sessionId,
13224
13369
  streamSid,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.330",
3
+ "version": "0.0.22-beta.332",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",