@absolutejs/voice 0.0.22-beta.246 → 0.0.22-beta.248

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
@@ -37,7 +37,7 @@ These are the primitive-first paths a Vapi-style buyer usually needs. Each path
37
37
  | Phone voice assistant | `createVoicePhoneAgent(...)` | carrier matrix, setup instructions, phone smoke contract, production readiness, operations record | phone setup HTML/JSON, smoke HTML/JSON, framework status UI |
38
38
  | Multi-specialist support flow | `createVoiceAgentSquad(...)` | squad contract, handoff traces, context traces, operations record | Agent Squad status hooks/composables/services/widgets |
39
39
  | Business actions and tools | `createVoiceAgentTool(...)` plus agent tool runtime | tool contracts, audit events, integration events, outcome contracts | operations record, action center, contract routes |
40
- | Guardrails and policy checks | `createVoiceGuardrailPolicy(...)` plus `createVoiceGuardrailRoutes(...)` | blocking/warning decisions, redacted content, `assistant.guardrail` trace events | guardrail JSON/Markdown routes and operations record traces |
40
+ | Guardrails and policy checks | `createVoiceGuardrailPolicy(...)`, `createVoiceGuardrailRuntime(...)`, and `createVoiceGuardrailRoutes(...)` | live assistant/tool enforcement, blocking/warning decisions, redacted content, `assistant.guardrail` trace events | guardrail JSON/Markdown routes and operations record traces |
41
41
  | Provider routing and fallback | provider routers, health checks, simulation controls | provider contract matrix, provider-stage traces, latency SLO reports | provider contract hooks/composables/services/widgets |
42
42
  | Production operations | ops status, ops recovery, production readiness, delivery runtime | readiness gates, recovery report, incident Markdown, delivery queues | ops action center, delivery runtime UI, operations record |
43
43
  | Outbound campaigns | `createVoiceCampaignRoutes(...)` | recipient validation, consent/dedupe, carrier dry-run, campaign readiness | campaign routes and operations-record-linked attempt proof |
@@ -116,14 +116,33 @@ app.use(
116
116
 
117
117
  ## Guardrails
118
118
 
119
- Use `createVoiceGuardrailRoutes(...)` when you need code-owned policy checks for what an agent may say, what tool payloads may contain, or which transcript content should warn/redact before downstream workflow. The primitive does not force a moderation vendor or hosted dashboard; it returns JSON/Markdown proof and can emit `assistant.guardrail` trace events.
119
+ Use `createVoiceGuardrailRuntime(...)` when you need code-owned live enforcement for what an agent may say, what tool payloads may contain, or which transcript content should warn/redact before downstream workflow. Use `createVoiceGuardrailRoutes(...)` beside it when you also want JSON/Markdown proof. The primitive does not force a moderation vendor or hosted dashboard; it emits `assistant.guardrail` trace events from the runtime and route surfaces.
120
120
 
121
121
  ```ts
122
122
  import {
123
+ createVoiceGuardrailRuntime,
123
124
  createVoiceGuardrailRoutes,
124
125
  voiceGuardrailPolicyPresets
125
126
  } from '@absolutejs/voice';
126
127
 
128
+ const guardrails = createVoiceGuardrailRuntime({
129
+ blockResult: ({ decision }) => ({
130
+ assistantText: 'I need to route this to a human specialist.',
131
+ escalate: {
132
+ reason: `guardrail-blocked-${decision.stage}`
133
+ }
134
+ }),
135
+ policies: [voiceGuardrailPolicyPresets.supportSafeDefaults],
136
+ trace: runtime.traces
137
+ });
138
+
139
+ const assistant = createVoiceAssistant({
140
+ guardrails: guardrails.assistantGuardrails,
141
+ id: 'support',
142
+ model,
143
+ tools: guardrails.wrapTools([lookupCustomerTool, createTicketTool])
144
+ });
145
+
127
146
  app.use(
128
147
  createVoiceGuardrailRoutes({
129
148
  path: '/api/voice/guardrails',
@@ -2773,7 +2792,7 @@ app.use(
2773
2792
  );
2774
2793
  ```
2775
2794
 
2776
- `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.
2795
+ `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. 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 an `assistant.guardrail` blocked-stage summary when guardrail trace events exist.
2777
2796
 
2778
2797
  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.
2779
2798
 
package/dist/index.d.ts CHANGED
@@ -56,7 +56,7 @@ export { buildVoiceProductionReadinessGate, buildVoiceProductionReadinessReport,
56
56
  export { createVoiceReadinessProfile, recommendVoiceReadinessProfile } from './readinessProfiles';
57
57
  export { buildVoiceProviderContractMatrix, createVoiceProviderContractMatrixHTMLHandler, createVoiceProviderContractMatrixJSONHandler, createVoiceProviderContractMatrixPreset, createVoiceProviderContractMatrixRoutes, evaluateVoiceProviderStackGaps, renderVoiceProviderContractMatrixHTML, recommendVoiceProviderStack } from './providerStackRecommendations';
58
58
  export { buildVoiceOpsConsoleReport, createVoiceOpsConsoleRoutes, renderVoiceOpsConsoleHTML } from './opsConsoleRoutes';
59
- export { buildVoiceOperationsRecord, createVoiceOperationsRecordRoutes, renderVoiceOperationsRecordHTML, renderVoiceOperationsRecordIncidentMarkdown } from './operationsRecord';
59
+ export { buildVoiceOperationsRecord, createVoiceOperationsRecordRoutes, renderVoiceOperationsRecordGuardrailMarkdown, renderVoiceOperationsRecordHTML, renderVoiceOperationsRecordIncidentMarkdown } from './operationsRecord';
60
60
  export { assertVoiceObservabilityExportRecord, buildVoiceObservabilityArtifactIndex, buildVoiceObservabilityExportDeliveryHistory, buildVoiceObservabilityExportReplayReport, buildVoiceObservabilityExport, assertVoiceObservabilityExportSchema, createVoiceObservabilityExportSchema, createVoiceFileObservabilityExportDeliveryReceiptStore, createVoiceMemoryObservabilityExportDeliveryReceiptStore, createVoiceObservabilityExportRoutes, createVoiceObservabilityExportReplayRoutes, deliverVoiceObservabilityExport, loadVoiceObservabilityExportReplaySource, replayVoiceObservabilityExport, renderVoiceObservabilityExportReplayHTML, renderVoiceObservabilityExportMarkdown, validateVoiceObservabilityExportRecord, voiceObservabilityExportSchemaId, voiceObservabilityExportSchemaVersion } from './observabilityExport';
61
61
  export { buildVoiceOpsRecoveryReadinessCheck, buildVoiceOpsRecoveryReport, createVoiceOpsRecoveryRoutes, renderVoiceOpsRecoveryHTML, renderVoiceOpsRecoveryMarkdown } from './opsRecovery';
62
62
  export { buildVoiceIncidentBundle, createStoredVoiceIncidentBundleArtifact, createVoiceIncidentBundleRoutes, createVoiceMemoryIncidentBundleStore, pruneVoiceIncidentBundleArtifacts, saveVoiceIncidentBundleArtifact } from './incidentBundle';
@@ -123,7 +123,7 @@ export type { VoiceOpsStatus, VoiceOpsStatusLink, VoiceOpsStatusOptions, VoiceOp
123
123
  export type { VoiceProductionReadinessAction, VoiceProductionReadinessAuditOptions, VoiceProductionReadinessAuditRequirement, VoiceProductionReadinessAuditSummary, VoiceProductionReadinessCheck, VoiceProductionReadinessGateIssue, VoiceProductionReadinessGateOptions, VoiceProductionReadinessGateProfile, VoiceProductionReadinessGateProfileSurface, VoiceProductionReadinessGateReport, VoiceProductionReadinessOpsActionHistoryOptions, VoiceProductionReadinessOpsActionHistorySummary, VoiceProductionReadinessOperationsRecordLink, VoiceProductionReadinessOperationsRecordLinks, VoiceProductionReadinessProfileExplanation, VoiceProductionReadinessProfileSurface, VoiceProductionReadinessProofSource, VoiceProductionReadinessReport, VoiceProductionReadinessRoutesOptions, VoiceProductionReadinessTraceDeliverySummary, VoiceProductionReadinessAuditDeliveryOptions, VoiceProductionReadinessAuditDeliverySummary, VoiceProductionReadinessTraceDeliveryOptions, VoiceProductionReadinessStatus } from './productionReadiness';
124
124
  export type { VoiceReadinessProfileName, VoiceReadinessProfileOptions, VoiceReadinessProfileRecommendation, VoiceReadinessProfileRecommendationScore, VoiceReadinessProfileRoutesOptions } from './readinessProfiles';
125
125
  export type { VoiceProviderStackChoice, VoiceProviderStackCapabilities, VoiceProviderStackCapabilityGap, VoiceProviderStackCapabilityGapInput, VoiceProviderStackCapabilityGapReport, VoiceProviderContractCheck, VoiceProviderContractCheckStatus, VoiceProviderContractDefinition, VoiceProviderContractMatrixHandlerOptions, VoiceProviderContractMatrixHTMLHandlerOptions, VoiceProviderContractMatrixInput, VoiceProviderContractMatrixPresetOptions, VoiceProviderContractMatrixReport, VoiceProviderContractMatrixRoutesOptions, VoiceProviderContractMatrixRow, VoiceProviderStackInput, VoiceProviderStackKind, VoiceProviderStackRecommendation } from './providerStackRecommendations';
126
- export type { VoiceOperationsRecord, VoiceOperationsRecordAgentHandoff, VoiceOperationsRecordAuditSummary, VoiceOperationsRecordIntegrationEventSummary, VoiceOperationsRecordOptions, VoiceOperationsRecordOutcome, VoiceOperationsRecordProviderDecision, VoiceOperationsRecordReviewSummary, VoiceOperationsRecordRoutesOptions, VoiceOperationsRecordStatus, VoiceOperationsRecordTaskSummary, VoiceOperationsRecordTranscriptTurn, VoiceOperationsRecordTool } from './operationsRecord';
126
+ export type { VoiceOperationsRecord, VoiceOperationsRecordAgentHandoff, VoiceOperationsRecordAuditSummary, VoiceOperationsRecordGuardrailDecision, VoiceOperationsRecordGuardrailFinding, VoiceOperationsRecordGuardrailSummary, VoiceOperationsRecordIntegrationEventSummary, VoiceOperationsRecordOptions, VoiceOperationsRecordOutcome, VoiceOperationsRecordProviderDecision, VoiceOperationsRecordReviewSummary, VoiceOperationsRecordRoutesOptions, VoiceOperationsRecordStatus, VoiceOperationsRecordTaskSummary, VoiceOperationsRecordTranscriptTurn, VoiceOperationsRecordTool } from './operationsRecord';
127
127
  export type { VoiceObservabilityExportArtifact, VoiceObservabilityExportArtifactChecksum, VoiceObservabilityExportArtifactFreshness, VoiceObservabilityExportArtifactIndex, VoiceObservabilityExportArtifactIndexItem, VoiceObservabilityExportArtifactKind, VoiceObservabilityExportDeliverySummary, VoiceObservabilityExportDeliveryDestination, VoiceObservabilityExportDeliveryDestinationResult, VoiceObservabilityExportDeliveryHistory, VoiceObservabilityExportDeliveryOptions, VoiceObservabilityExportDeliveryReceipt, VoiceObservabilityExportDeliveryReceiptStore, VoiceObservabilityExportDeliveryReport, VoiceObservabilityExportEnvelope, VoiceObservabilityExportIssue, VoiceObservabilityExportIssueCode, VoiceObservabilityExportOptions, VoiceObservabilityExportIngestedRecordKind, VoiceObservabilityExportRedactionSummary, VoiceObservabilityExportRecordValidationOptions, VoiceObservabilityExportReplayIssue, VoiceObservabilityExportReplayIssueCode, VoiceObservabilityExportReplayRecords, VoiceObservabilityExportReplayReport, VoiceObservabilityExportReplayRoutesOptions, VoiceObservabilityExportReplaySource, VoiceObservabilityExportReport, VoiceObservabilityExportRoutesOptions, VoiceObservabilityExportSchema, VoiceObservabilityExportStatus, VoiceObservabilityExportValidationIssue, VoiceObservabilityExportValidationResult } from './observabilityExport';
128
128
  export type { VoiceOpsRecoveryFailedSession, VoiceOpsRecoveryInterventionSummary, VoiceOpsRecoveryIssue, VoiceOpsRecoveryIssueCode, VoiceOpsRecoveryLinks, VoiceOpsRecoveryProviderSummary, VoiceOpsRecoveryReport, VoiceOpsRecoveryReportOptions, VoiceOpsRecoveryRoutesOptions, VoiceOpsRecoveryStatus } from './opsRecovery';
129
129
  export type { StoredVoiceIncidentBundleArtifact, VoiceIncidentBundle, VoiceIncidentBundleArtifactOptions, VoiceIncidentBundleFormat, VoiceIncidentBundleOptions, VoiceIncidentBundleRetentionOptions, VoiceIncidentBundleRetentionReport, VoiceIncidentBundleRoutesOptions, VoiceIncidentBundleStore, VoiceIncidentBundleStoreFilter, VoiceIncidentBundleSummary } from './incidentBundle';
package/dist/index.js CHANGED
@@ -22872,6 +22872,7 @@ var createVoiceTraceTimelineRoutes = (options) => {
22872
22872
  // src/operationsRecord.ts
22873
22873
  var getString16 = (value) => typeof value === "string" ? value : undefined;
22874
22874
  var getNumber9 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
22875
+ var getBoolean3 = (value) => typeof value === "boolean" ? value : undefined;
22875
22876
  var countOutcome = (events, outcome) => events.filter((event) => event.outcome === outcome).length;
22876
22877
  var matchesSessionScopedId = (id, sessionId) => id === sessionId || id.startsWith(`${sessionId}:`);
22877
22878
  var hasPayloadValue = (payload, key, values) => {
@@ -22899,6 +22900,44 @@ var toTool = (event) => ({
22899
22900
  toolName: getString16(event.payload.toolName),
22900
22901
  turnId: event.turnId
22901
22902
  });
22903
+ var toGuardrailFinding = (value) => {
22904
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
22905
+ return;
22906
+ }
22907
+ const record = value;
22908
+ return {
22909
+ action: getString16(record.action),
22910
+ label: getString16(record.label),
22911
+ ruleId: getString16(record.ruleId)
22912
+ };
22913
+ };
22914
+ var toGuardrailDecision = (event) => ({
22915
+ allowed: getBoolean3(event.payload.allowed),
22916
+ at: event.at,
22917
+ findings: Array.isArray(event.payload.findings) ? event.payload.findings.map(toGuardrailFinding).filter((finding) => finding !== undefined) : [],
22918
+ metadata: event.metadata && typeof event.metadata === "object" && !Array.isArray(event.metadata) ? event.metadata : undefined,
22919
+ proof: getString16(event.metadata?.proof),
22920
+ stage: getString16(event.payload.stage),
22921
+ status: getString16(event.payload.status),
22922
+ toolName: getString16(event.payload.toolName),
22923
+ turnId: event.turnId
22924
+ });
22925
+ var summarizeGuardrails = (events) => {
22926
+ const decisions = events.filter((event) => event.type === "assistant.guardrail").map(toGuardrailDecision);
22927
+ const isBlocked = (decision) => decision.allowed === false || decision.status === "fail";
22928
+ const isWarned = (decision) => decision.status === "warn" || decision.findings.some((finding) => finding.action === "warn");
22929
+ const stages = [
22930
+ ...new Set(decisions.map((decision) => decision.stage).filter((stage) => typeof stage === "string"))
22931
+ ].sort();
22932
+ return {
22933
+ blocked: decisions.filter(isBlocked).length,
22934
+ decisions,
22935
+ passed: decisions.filter((decision) => !isBlocked(decision) && !isWarned(decision)).length,
22936
+ stages,
22937
+ total: decisions.length,
22938
+ warned: decisions.filter(isWarned).length
22939
+ };
22940
+ };
22902
22941
  var toProviderDecision = (event) => {
22903
22942
  const provider = getString16(event.payload.provider) ?? getString16(event.payload.selectedProvider) ?? getString16(event.payload.fallbackProvider) ?? getString16(event.payload.variantId);
22904
22943
  const status = getString16(event.payload.providerStatus) ?? getString16(event.payload.status) ?? getString16(event.payload.reason);
@@ -22971,6 +23010,7 @@ var buildVoiceOperationsRecord = async (options) => {
22971
23010
  total: auditEvents.length
22972
23011
  } : undefined,
22973
23012
  checkedAt: Date.now(),
23013
+ guardrails: summarizeGuardrails(traceEvents),
22974
23014
  handoffs: traceEvents.filter((event) => event.type === "agent.handoff").map(toHandoff),
22975
23015
  integrationEvents: integrationEvents ? {
22976
23016
  delivered: countIntegrationDeliveryStatus(integrationEvents, "delivered"),
@@ -23031,6 +23071,9 @@ var renderVoiceOperationsRecordIncidentMarkdown = (record) => {
23031
23071
  `- Providers: ${record.providers.map((provider) => provider.provider).join(", ") || "none recorded"}`,
23032
23072
  `- Open tasks: ${openTasks.join("; ") || "none"}`,
23033
23073
  `- Top errors: ${topErrors.join("; ") || "none"}`,
23074
+ `- Guardrails: ${String(record.guardrails.blocked)} blocked / ${String(record.guardrails.warned)} warned / ${String(record.guardrails.total)} decisions`,
23075
+ "",
23076
+ renderVoiceOperationsRecordGuardrailMarkdown(record),
23034
23077
  "",
23035
23078
  "## Next checks",
23036
23079
  "- Review provider decisions and fallback status.",
@@ -23039,6 +23082,25 @@ var renderVoiceOperationsRecordIncidentMarkdown = (record) => {
23039
23082
  ].join(`
23040
23083
  `);
23041
23084
  };
23085
+ var renderVoiceOperationsRecordGuardrailMarkdown = (record) => {
23086
+ if (record.guardrails.total === 0) {
23087
+ return [
23088
+ "## Guardrail evidence",
23089
+ "",
23090
+ "- No assistant.guardrail events were recorded for this session."
23091
+ ].join(`
23092
+ `);
23093
+ }
23094
+ return [
23095
+ "## Guardrail evidence",
23096
+ "",
23097
+ ...record.guardrails.decisions.map((decision) => {
23098
+ const findings = decision.findings.map((finding) => [finding.action, finding.ruleId, finding.label].filter((value) => typeof value === "string").join(":")).filter(Boolean).join(", ");
23099
+ return `- assistant.guardrail ${decision.stage ?? "unknown"}: ${decision.status ?? "unknown"}; allowed=${String(decision.allowed ?? "unknown")}; proof=${decision.proof ?? "runtime"}; findings=${findings || "none"}`;
23100
+ })
23101
+ ].join(`
23102
+ `);
23103
+ };
23042
23104
  var renderVoiceOperationsRecordHTML = (record, options = {}) => {
23043
23105
  const providers = record.providers.length ? record.providers.map((provider) => `<article><strong>${escapeHtml39(provider.provider)}</strong><span>${String(provider.eventCount)} events</span><span>${formatMs4(provider.averageElapsedMs)} avg</span><span>${String(provider.errorCount)} errors</span></article>`).join("") : '<p class="muted">No provider events recorded.</p>';
23044
23106
  const transcript = record.transcript.length ? record.transcript.map((turn) => `<li><strong>${escapeHtml39(turn.id)}</strong>${turn.committedText ? `<p><span class="label">Caller</span>${escapeHtml39(turn.committedText)}</p>` : ""}${turn.assistantReplies.map((reply) => `<p><span class="label">Assistant</span>${escapeHtml39(reply)}</p>`).join("")}${turn.errors.map((error) => `<p class="error"><span class="label">Error</span>${escapeHtml39(error)}</p>`).join("")}</li>`).join("") : "<li>No transcript turns recorded.</li>";
@@ -23048,6 +23110,10 @@ var renderVoiceOperationsRecordHTML = (record, options = {}) => {
23048
23110
  const reviews = record.reviews?.reviews.length ? record.reviews.reviews.map((review) => `<li><strong>${escapeHtml39(review.title)}</strong> <span>${escapeHtml39(review.summary.outcome ?? "")}</span><p>${escapeHtml39(review.postCall?.summary ?? review.transcript.actual)}</p></li>`).join("") : "<li>No call reviews recorded.</li>";
23049
23111
  const tasks = record.tasks?.tasks.length ? record.tasks.tasks.map((task) => `<li><strong>${escapeHtml39(task.title)}</strong> <span>${escapeHtml39(task.status)}</span><p>${escapeHtml39(task.recommendedAction)}</p></li>`).join("") : "<li>No ops tasks recorded.</li>";
23050
23112
  const integrationEvents = record.integrationEvents?.events.length ? record.integrationEvents.events.map((event) => `<li><strong>${escapeHtml39(event.type)}</strong> <span>${escapeHtml39(event.deliveryStatus ?? "local")}</span><p>${escapeHtml39(event.deliveryError ?? event.deliveredTo ?? "")}</p></li>`).join("") : "<li>No integration events recorded.</li>";
23113
+ const guardrails = record.guardrails.total ? record.guardrails.decisions.map((decision) => {
23114
+ const findings = decision.findings.map((finding) => finding.label ?? finding.ruleId ?? finding.action).filter((value) => typeof value === "string").join(", ") || "none";
23115
+ return `<li><strong>assistant.guardrail ${escapeHtml39(decision.stage ?? "unknown")}</strong> <span>${escapeHtml39(decision.status ?? "")}</span><p>Allowed: ${escapeHtml39(String(decision.allowed ?? "unknown"))} \xB7 Proof: ${escapeHtml39(decision.proof ?? "runtime")}${decision.turnId ? ` \xB7 Turn: ${escapeHtml39(decision.turnId)}` : ""}</p><p>${escapeHtml39(findings)}</p></li>`;
23116
+ }).join("") : "<li>No assistant.guardrail events recorded.</li>";
23051
23117
  const snippet = escapeHtml39(`app.use(
23052
23118
  createVoiceOperationsRecordRoutes({
23053
23119
  audit: auditStore,
@@ -23064,7 +23130,7 @@ var renderVoiceOperationsRecordHTML = (record, options = {}) => {
23064
23130
  );`);
23065
23131
  const incidentMarkdown = escapeHtml39(renderVoiceOperationsRecordIncidentMarkdown(record));
23066
23132
  const incidentLink = options.incidentHref ? `<a href="${escapeHtml39(options.incidentHref)}">Download incident.md</a>` : "";
23067
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml39(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>${escapeHtml39(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml39(record.status)}">${escapeHtml39(record.status)}</p><div class="hero-actions"><a href="#transcript">Transcript</a><a href="#provider-decisions">Provider decisions</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>${formatMs4(record.summary.callDurationMs)}</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="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, 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>`;
23133
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml39(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>${escapeHtml39(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml39(record.status)}">${escapeHtml39(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>${formatMs4(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>`;
23068
23134
  };
23069
23135
  var createVoiceOperationsRecordRoutes = (options) => {
23070
23136
  const path = options.path ?? "/api/voice-operations/:sessionId";
@@ -26686,6 +26752,8 @@ var renderIncidentMarkdown = (input) => {
26686
26752
  "",
26687
26753
  ...input.record.tools.length ? input.record.tools.map((tool) => `- ${tool.toolName ?? "tool"} ${tool.status ?? ""} ${tool.elapsedMs === undefined ? "" : `${tool.elapsedMs}ms`} ${tool.error ?? ""}`.trim()) : ["- none"],
26688
26754
  "",
26755
+ renderVoiceOperationsRecordGuardrailMarkdown(input.record),
26756
+ "",
26689
26757
  "## Trace Evidence",
26690
26758
  "",
26691
26759
  input.traceMarkdown,
@@ -29771,6 +29839,7 @@ export {
29771
29839
  renderVoiceOpsActionHistoryHTML,
29772
29840
  renderVoiceOperationsRecordIncidentMarkdown,
29773
29841
  renderVoiceOperationsRecordHTML,
29842
+ renderVoiceOperationsRecordGuardrailMarkdown,
29774
29843
  renderVoiceObservabilityExportReplayHTML,
29775
29844
  renderVoiceObservabilityExportMarkdown,
29776
29845
  renderVoiceLiveLatencyHTML,
@@ -81,9 +81,34 @@ export type VoiceOperationsRecordIntegrationEventSummary = {
81
81
  skipped: number;
82
82
  total: number;
83
83
  };
84
+ export type VoiceOperationsRecordGuardrailFinding = {
85
+ action?: string;
86
+ label?: string;
87
+ ruleId?: string;
88
+ };
89
+ export type VoiceOperationsRecordGuardrailDecision = {
90
+ allowed?: boolean;
91
+ at: number;
92
+ findings: VoiceOperationsRecordGuardrailFinding[];
93
+ metadata?: Record<string, unknown>;
94
+ proof?: string;
95
+ stage?: string;
96
+ status?: string;
97
+ toolName?: string;
98
+ turnId?: string;
99
+ };
100
+ export type VoiceOperationsRecordGuardrailSummary = {
101
+ blocked: number;
102
+ decisions: VoiceOperationsRecordGuardrailDecision[];
103
+ passed: number;
104
+ stages: string[];
105
+ total: number;
106
+ warned: number;
107
+ };
84
108
  export type VoiceOperationsRecord = {
85
109
  audit?: VoiceOperationsRecordAuditSummary;
86
110
  checkedAt: number;
111
+ guardrails: VoiceOperationsRecordGuardrailSummary;
87
112
  handoffs: VoiceOperationsRecordAgentHandoff[];
88
113
  integrationEvents?: VoiceOperationsRecordIntegrationEventSummary;
89
114
  outcome: VoiceOperationsRecordOutcome;
@@ -124,6 +149,7 @@ export type VoiceOperationsRecordRoutesOptions = Omit<VoiceOperationsRecordOptio
124
149
  };
125
150
  export declare const buildVoiceOperationsRecord: (options: VoiceOperationsRecordOptions) => Promise<VoiceOperationsRecord>;
126
151
  export declare const renderVoiceOperationsRecordIncidentMarkdown: (record: VoiceOperationsRecord) => string;
152
+ export declare const renderVoiceOperationsRecordGuardrailMarkdown: (record: VoiceOperationsRecord) => string;
127
153
  export declare const renderVoiceOperationsRecordHTML: (record: VoiceOperationsRecord, options?: {
128
154
  incidentHref?: string;
129
155
  title?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.246",
3
+ "version": "0.0.22-beta.248",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",