@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 +1 -1
- package/dist/index.js +37 -2
- package/dist/operationsRecord.d.ts +12 -0
- package/package.json +1 -1
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
|
|
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;
|