@absolutejs/voice 0.0.22-beta.298 → 0.0.22-beta.299

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
@@ -2807,7 +2807,7 @@ app.use(
2807
2807
  );
2808
2808
  ```
2809
2809
 
2810
- `createVoiceOperationsRecordRoutes(...)` links the call/session timeline, transcript, replay, provider decisions, tools, handoffs, guardrail decisions, audit, reviews, ops tasks, integration events, and sink delivery attempts into one debuggable object. Provider decisions include both older provider-routing events and explicit `provider.decision` traces, so the call log can show the surface, selected provider, fallback provider, and human-readable reason for each runtime choice. Use `/voice-operations/:sessionId` as the first place to investigate failed calls, blocked assistant output, blocked tool payloads, 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, including provider-decision summaries and an `assistant.guardrail` blocked-stage summary when those trace events exist.
2810
+ `createVoiceOperationsRecordRoutes(...)` links the call/session timeline, transcript, replay, provider decisions, tools, handoffs, guardrail decisions, audit, reviews, ops tasks, integration events, and sink delivery attempts into one debuggable object. Provider decisions include both older provider-routing events and explicit `provider.decision` traces, so the call log can show the surface, selected provider, fallback provider, recovery status, fallback/degradation counts, and human-readable reason for each runtime choice. Use `/voice-operations/:sessionId` as the first place to investigate failed calls, blocked assistant output, blocked tool payloads, 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, including provider-decision recovery summaries and an `assistant.guardrail` blocked-stage summary when those trace events exist.
2811
2811
 
2812
2812
  Use `evaluateVoiceOperationsRecordGuardrails(...)` when a proof pack or deploy gate needs JSON evidence that guardrails actually ran, blocked the expected stages, and produced named proofs/rule IDs. Use `assertVoiceOperationsRecordGuardrails(...)` in tests or smoke scripts when missing guardrail evidence should fail fast:
2813
2813
 
package/dist/index.js CHANGED
@@ -26324,6 +26324,29 @@ var toProviderDecision = (event) => {
26324
26324
  turnId: event.turnId
26325
26325
  };
26326
26326
  };
26327
+ var summarizeProviderDecisions = (decisions) => {
26328
+ const providers = uniqueSorted8(decisions.flatMap((decision) => [
26329
+ decision.provider,
26330
+ decision.selectedProvider,
26331
+ decision.fallbackProvider
26332
+ ]));
26333
+ const surfaces = uniqueSorted8(decisions.map((decision) => decision.surface));
26334
+ const degraded = decisions.filter((decision) => decision.status === "degraded").length;
26335
+ const errors = decisions.filter((decision) => decision.status === "error").length;
26336
+ const fallbacks = decisions.filter((decision) => decision.status === "fallback").length;
26337
+ const selected = decisions.filter((decision) => decision.status === "selected" || decision.status === "success").length;
26338
+ const recoveryStatus = errors > 0 ? "failed" : degraded > 0 ? "degraded" : fallbacks > 0 ? "recovered" : selected > 0 ? "selected" : "none";
26339
+ return {
26340
+ degraded,
26341
+ errors,
26342
+ fallbacks,
26343
+ providers,
26344
+ recoveryStatus,
26345
+ selected,
26346
+ surfaces,
26347
+ total: decisions.length
26348
+ };
26349
+ };
26327
26350
  var buildTranscript = (replay) => replay.turns.map((turn) => ({
26328
26351
  assistantReplies: turn.assistantReplies,
26329
26352
  committedText: turn.committedText,
@@ -26366,6 +26389,7 @@ var buildVoiceOperationsRecord = async (options) => {
26366
26389
  const taskIds = new Set(tasks?.map((task) => task.id) ?? []);
26367
26390
  const integrationEvents = options.integrationEvents ? (await options.integrationEvents.list()).filter((event) => hasPayloadValue(event.payload, "sessionId", new Set([options.sessionId])) || hasPayloadValue(event.payload, "reviewId", reviewIds) || hasPayloadValue(event.payload, "taskId", taskIds)) : undefined;
26368
26391
  const sinkDeliveries = integrationEvents?.reduce((total, event) => total + Object.keys(event.sinkDeliveries ?? {}).length, 0) ?? 0;
26392
+ const providerDecisions = traceEvents.map(toProviderDecision).filter((decision) => decision !== undefined);
26369
26393
  return {
26370
26394
  audit: auditEvents ? {
26371
26395
  error: countOutcome(auditEvents, "error"),
@@ -26387,7 +26411,8 @@ var buildVoiceOperationsRecord = async (options) => {
26387
26411
  total: integrationEvents.length
26388
26412
  } : undefined,
26389
26413
  outcome: resolveOutcome4(traceEvents),
26390
- providerDecisions: traceEvents.map(toProviderDecision).filter((decision) => decision !== undefined),
26414
+ providerDecisions,
26415
+ providerDecisionSummary: summarizeProviderDecisions(providerDecisions),
26391
26416
  providers: timelineSession?.providers ?? [],
26392
26417
  replay,
26393
26418
  reviews: reviews ? {
@@ -26509,6 +26534,14 @@ var renderVoiceOperationsRecordIncidentMarkdown = (record) => {
26509
26534
  ].filter((part) => typeof part === "string");
26510
26535
  return `- ${provider}: ${parts.join("; ") || "decision recorded"}`;
26511
26536
  }) : ["- none recorded"];
26537
+ const providerDecisionSummary = record.providerDecisionSummary;
26538
+ const providerRecoveryLine = [
26539
+ `status=${providerDecisionSummary.recoveryStatus}`,
26540
+ `selected=${String(providerDecisionSummary.selected)}`,
26541
+ `fallbacks=${String(providerDecisionSummary.fallbacks)}`,
26542
+ `degraded=${String(providerDecisionSummary.degraded)}`,
26543
+ `errors=${String(providerDecisionSummary.errors)}`
26544
+ ].join("; ");
26512
26545
  return [
26513
26546
  `# Voice incident handoff: ${record.sessionId}`,
26514
26547
  "",
@@ -26521,6 +26554,7 @@ var renderVoiceOperationsRecordIncidentMarkdown = (record) => {
26521
26554
  `- Open tasks: ${openTasks.join("; ") || "none"}`,
26522
26555
  `- Top errors: ${topErrors.join("; ") || "none"}`,
26523
26556
  `- Guardrails: ${String(record.guardrails.blocked)} blocked / ${String(record.guardrails.warned)} warned / ${String(record.guardrails.total)} decisions`,
26557
+ `- Provider recovery: ${providerRecoveryLine}`,
26524
26558
  "",
26525
26559
  "## Provider decisions",
26526
26560
  "",
@@ -26558,6 +26592,7 @@ var renderVoiceOperationsRecordHTML = (record, options = {}) => {
26558
26592
  const providers = record.providers.length ? record.providers.map((provider) => `<article><strong>${escapeHtml42(provider.provider)}</strong><span>${String(provider.eventCount)} events</span><span>${formatMs5(provider.averageElapsedMs)} avg</span><span>${String(provider.errorCount)} errors</span></article>`).join("") : '<p class="muted">No provider events recorded.</p>';
26559
26593
  const transcript = record.transcript.length ? record.transcript.map((turn) => `<li><strong>${escapeHtml42(turn.id)}</strong>${turn.committedText ? `<p><span class="label">Caller</span>${escapeHtml42(turn.committedText)}</p>` : ""}${turn.assistantReplies.map((reply) => `<p><span class="label">Assistant</span>${escapeHtml42(reply)}</p>`).join("")}${turn.errors.map((error) => `<p class="error"><span class="label">Error</span>${escapeHtml42(error)}</p>`).join("")}</li>`).join("") : "<li>No transcript turns recorded.</li>";
26560
26594
  const providerDecisions = record.providerDecisions.length ? record.providerDecisions.map((decision) => `<li><strong>${escapeHtml42(decision.provider ?? decision.selectedProvider ?? decision.fallbackProvider ?? "provider")}</strong> <span>${escapeHtml42(decision.status ?? decision.type)}</span> ${formatMs5(decision.elapsedMs)}${decision.surface ? `<p><span class="label">Surface</span>${escapeHtml42(decision.surface)}</p>` : ""}${decision.kind ? `<p><span class="label">Kind</span>${escapeHtml42(decision.kind)}</p>` : ""}${decision.selectedProvider ? `<p>Selected: ${escapeHtml42(decision.selectedProvider)}</p>` : ""}${decision.fallbackProvider ? `<p>Fallback: ${escapeHtml42(decision.fallbackProvider)}</p>` : ""}${decision.error ? `<p class="error">${escapeHtml42(decision.error)}</p>` : ""}${decision.reason ? `<p>${escapeHtml42(decision.reason)}</p>` : ""}</li>`).join("") : "<li>No provider decisions recorded.</li>";
26595
+ const providerDecisionSummary = record.providerDecisionSummary;
26561
26596
  const handoffs = record.handoffs.length ? record.handoffs.map((handoff) => `<li><strong>${escapeHtml42(handoff.fromAgentId ?? "unknown")}</strong> to <strong>${escapeHtml42(handoff.targetAgentId ?? "unknown")}</strong> <span>${escapeHtml42(handoff.status ?? "")}</span><p>${escapeHtml42(handoff.summary ?? handoff.reason ?? "")}</p></li>`).join("") : "<li>No agent handoffs recorded.</li>";
26562
26597
  const tools = record.tools.length ? record.tools.map((tool) => `<li><strong>${escapeHtml42(tool.toolName ?? "tool")}</strong> <span>${escapeHtml42(tool.status ?? "")}</span> ${formatMs5(tool.elapsedMs)} ${tool.error ? `<p>${escapeHtml42(tool.error)}</p>` : ""}</li>`).join("") : "<li>No tool calls recorded.</li>";
26563
26598
  const reviews = record.reviews?.reviews.length ? record.reviews.reviews.map((review) => `<li><strong>${escapeHtml42(review.title)}</strong> <span>${escapeHtml42(review.summary.outcome ?? "")}</span><p>${escapeHtml42(review.postCall?.summary ?? review.transcript.actual)}</p></li>`).join("") : "<li>No call reviews recorded.</li>";
@@ -26583,7 +26618,7 @@ var renderVoiceOperationsRecordHTML = (record, options = {}) => {
26583
26618
  );`);
26584
26619
  const incidentMarkdown = escapeHtml42(renderVoiceOperationsRecordIncidentMarkdown(record));
26585
26620
  const incidentLink = options.incidentHref ? `<a href="${escapeHtml42(options.incidentHref)}">Download incident.md</a>` : "";
26586
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml42(options.title ?? "Voice Operations Record")}</title><style>body{background:#101417;color:#f9f4e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.eyebrow{color:#fbbf24;font-size:.8rem;font-weight:900;letter-spacing:.14em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.8rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:20px 0}.card,.primitive{background:#182025;border:1px solid #2d3a43;border-radius:20px;padding:16px}.card span,.muted,.label{color:#a9b4bd}.label{display:block;font-size:.72rem;font-weight:900;letter-spacing:.12em;text-transform:uppercase}.card strong{display:block;font-size:2rem}section{margin-top:28px}article{display:grid;gap:8px}ul{display:grid;gap:10px;list-style:none;padding:0}li{background:#182025;border:1px solid #2d3a43;border-radius:16px;padding:14px}pre{background:#080d10;border:1px solid #2d3a43;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}.hero-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:16px}.hero-actions a{background:#fbbf24;border-radius:999px;color:#111827;font-weight:900;padding:10px 14px;text-decoration:none}.two-column{display:grid;gap:18px;grid-template-columns:minmax(0,1.15fr) minmax(280px,.85fr)}@media(max-width:860px){main{padding:20px}.two-column{grid-template-columns:1fr}}</style></head><body><main><p class="eyebrow">Call log replacement</p><h1>${escapeHtml42(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml42(record.status)}">${escapeHtml42(record.status)}</p><div class="hero-actions"><a href="#transcript">Transcript</a><a href="#provider-decisions">Provider decisions</a><a href="#guardrails">Guardrails</a><a href="#incident-handoff">Incident handoff</a>${incidentLink}</div><section class="grid"><div class="card"><span>Events</span><strong>${String(record.summary.eventCount)}</strong></div><div class="card"><span>Turns</span><strong>${String(record.summary.turnCount)}</strong></div><div class="card"><span>Errors</span><strong>${String(record.summary.errorCount)}</strong></div><div class="card"><span>Duration</span><strong>${formatMs5(record.summary.callDurationMs)}</strong></div><div class="card"><span>Guardrails</span><strong>${String(record.guardrails.blocked)}</strong></div><div class="card"><span>Audit</span><strong>${String(record.audit?.total ?? 0)}</strong></div><div class="card"><span>Reviews</span><strong>${String(record.reviews?.total ?? 0)}</strong></div><div class="card"><span>Tasks</span><strong>${String(record.tasks?.total ?? 0)}</strong></div><div class="card"><span>Integrations</span><strong>${String(record.integrationEvents?.total ?? 0)}</strong></div></section><section class="two-column"><div><h2 id="transcript">Transcript</h2><ul>${transcript}</ul></div><div><h2 id="provider-decisions">Provider Decisions</h2><ul>${providerDecisions}</ul></div></section><section id="guardrails"><h2>Guardrail Evidence</h2><p class="muted">Live <code>assistant.guardrail</code> decisions attached to this session.</p><ul>${guardrails}</ul></section><section id="incident-handoff"><h2>Copyable Incident Handoff</h2><p class="muted">Paste this into Slack, Linear, Zendesk, or an incident review. ${incidentLink}</p><pre><code>${incidentMarkdown}</code></pre></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceOperationsRecordRoutes(...)</code> gives every call one debuggable object</h2><p class="muted">Use this as the support/debug payload across traces, provider routing, tools, handoffs, guardrails, audit, latency, replay, reviews, tasks, and webhook delivery.</p><pre><code>${snippet}</code></pre></section><section><h2>Provider Summary</h2><div class="grid">${providers}</div></section><section><h2>Handoffs</h2><ul>${handoffs}</ul></section><section><h2>Tools</h2><ul>${tools}</ul></section><section><h2>Reviews</h2><ul>${reviews}</ul></section><section><h2>Tasks</h2><ul>${tasks}</ul></section><section><h2>Integration Events</h2><ul>${integrationEvents}</ul></section></main></body></html>`;
26621
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml42(options.title ?? "Voice Operations Record")}</title><style>body{background:#101417;color:#f9f4e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.eyebrow{color:#fbbf24;font-size:.8rem;font-weight:900;letter-spacing:.14em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.8rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:20px 0}.card,.primitive{background:#182025;border:1px solid #2d3a43;border-radius:20px;padding:16px}.card span,.muted,.label{color:#a9b4bd}.label{display:block;font-size:.72rem;font-weight:900;letter-spacing:.12em;text-transform:uppercase}.card strong{display:block;font-size:2rem}section{margin-top:28px}article{display:grid;gap:8px}ul{display:grid;gap:10px;list-style:none;padding:0}li{background:#182025;border:1px solid #2d3a43;border-radius:16px;padding:14px}pre{background:#080d10;border:1px solid #2d3a43;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}.hero-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:16px}.hero-actions a{background:#fbbf24;border-radius:999px;color:#111827;font-weight:900;padding:10px 14px;text-decoration:none}.two-column{display:grid;gap:18px;grid-template-columns:minmax(0,1.15fr) minmax(280px,.85fr)}@media(max-width:860px){main{padding:20px}.two-column{grid-template-columns:1fr}}</style></head><body><main><p class="eyebrow">Call log replacement</p><h1>${escapeHtml42(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml42(record.status)}">${escapeHtml42(record.status)}</p><div class="hero-actions"><a href="#transcript">Transcript</a><a href="#provider-decisions">Provider decisions</a><a href="#guardrails">Guardrails</a><a href="#incident-handoff">Incident handoff</a>${incidentLink}</div><section class="grid"><div class="card"><span>Events</span><strong>${String(record.summary.eventCount)}</strong></div><div class="card"><span>Turns</span><strong>${String(record.summary.turnCount)}</strong></div><div class="card"><span>Errors</span><strong>${String(record.summary.errorCount)}</strong></div><div class="card"><span>Duration</span><strong>${formatMs5(record.summary.callDurationMs)}</strong></div><div class="card"><span>Provider recovery</span><strong>${escapeHtml42(providerDecisionSummary.recoveryStatus)}</strong><span>${String(providerDecisionSummary.fallbacks)} fallback / ${String(providerDecisionSummary.degraded)} degraded / ${String(providerDecisionSummary.errors)} errors</span></div><div class="card"><span>Guardrails</span><strong>${String(record.guardrails.blocked)}</strong></div><div class="card"><span>Audit</span><strong>${String(record.audit?.total ?? 0)}</strong></div><div class="card"><span>Reviews</span><strong>${String(record.reviews?.total ?? 0)}</strong></div><div class="card"><span>Tasks</span><strong>${String(record.tasks?.total ?? 0)}</strong></div><div class="card"><span>Integrations</span><strong>${String(record.integrationEvents?.total ?? 0)}</strong></div></section><section class="two-column"><div><h2 id="transcript">Transcript</h2><ul>${transcript}</ul></div><div><h2 id="provider-decisions">Provider Decisions</h2><ul>${providerDecisions}</ul></div></section><section id="guardrails"><h2>Guardrail Evidence</h2><p class="muted">Live <code>assistant.guardrail</code> decisions attached to this session.</p><ul>${guardrails}</ul></section><section id="incident-handoff"><h2>Copyable Incident Handoff</h2><p class="muted">Paste this into Slack, Linear, Zendesk, or an incident review. ${incidentLink}</p><pre><code>${incidentMarkdown}</code></pre></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceOperationsRecordRoutes(...)</code> gives every call one debuggable object</h2><p class="muted">Use this as the support/debug payload across traces, provider routing, tools, handoffs, guardrails, audit, latency, replay, reviews, tasks, and webhook delivery.</p><pre><code>${snippet}</code></pre></section><section><h2>Provider Summary</h2><div class="grid">${providers}</div></section><section><h2>Handoffs</h2><ul>${handoffs}</ul></section><section><h2>Tools</h2><ul>${tools}</ul></section><section><h2>Reviews</h2><ul>${reviews}</ul></section><section><h2>Tasks</h2><ul>${tasks}</ul></section><section><h2>Integration Events</h2><ul>${integrationEvents}</ul></section></main></body></html>`;
26587
26622
  };
26588
26623
  var createVoiceOperationsRecordRoutes = (options) => {
26589
26624
  const path = options.path ?? "/api/voice-operations/:sessionId";
@@ -54,6 +54,17 @@ export type VoiceOperationsRecordProviderDecision = {
54
54
  type: StoredVoiceTraceEvent['type'];
55
55
  turnId?: string;
56
56
  };
57
+ export type VoiceOperationsRecordProviderDecisionRecoveryStatus = 'degraded' | 'failed' | 'none' | 'recovered' | 'selected';
58
+ export type VoiceOperationsRecordProviderDecisionSummary = {
59
+ degraded: number;
60
+ errors: number;
61
+ fallbacks: number;
62
+ providers: string[];
63
+ recoveryStatus: VoiceOperationsRecordProviderDecisionRecoveryStatus;
64
+ selected: number;
65
+ surfaces: string[];
66
+ total: number;
67
+ };
57
68
  export type VoiceOperationsRecordAuditSummary = {
58
69
  error: number;
59
70
  events: StoredVoiceAuditEvent[];
@@ -139,6 +150,7 @@ export type VoiceOperationsRecord = {
139
150
  integrationEvents?: VoiceOperationsRecordIntegrationEventSummary;
140
151
  outcome: VoiceOperationsRecordOutcome;
141
152
  providerDecisions: VoiceOperationsRecordProviderDecision[];
153
+ providerDecisionSummary: VoiceOperationsRecordProviderDecisionSummary;
142
154
  providers: VoiceTraceTimelineProviderSummary[];
143
155
  replay: VoiceSessionReplay;
144
156
  reviews?: VoiceOperationsRecordReviewSummary;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.298",
3
+ "version": "0.0.22-beta.299",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",