@absolutejs/voice 0.0.22-beta.385 → 0.0.22-beta.387

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/dist/vue/index.js CHANGED
@@ -1415,7 +1415,1760 @@ var VoicePlatformCoverage = defineComponent4({
1415
1415
  import { defineComponent as defineComponent5, h as h5 } from "vue";
1416
1416
 
1417
1417
  // src/proofTrends.ts
1418
+ import { Elysia as Elysia4 } from "elysia";
1419
+
1420
+ // src/providerDecisionTraces.ts
1421
+ import { Elysia as Elysia3 } from "elysia";
1422
+
1423
+ // src/resilienceRoutes.ts
1424
+ import { Elysia as Elysia2 } from "elysia";
1425
+
1426
+ // src/providerHealth.ts
1418
1427
  import { Elysia } from "elysia";
1428
+ var getString = (value) => typeof value === "string" ? value : undefined;
1429
+ var getNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
1430
+ var isProviderStatus = (value) => value === "success" || value === "fallback" || value === "error";
1431
+ var summarizeVoiceProviderHealth = async (input) => {
1432
+ const options = Array.isArray(input) ? { events: input } : input;
1433
+ const events = options.events ?? await options.store?.list() ?? [];
1434
+ const providers = options.providers ?? [];
1435
+ const providerSet = new Set(providers);
1436
+ const now = options.now ?? Date.now();
1437
+ const entries = new Map;
1438
+ const isAllowedProvider = (value) => typeof value === "string" && (providerSet.size === 0 || providerSet.has(value));
1439
+ const getEntry = (provider) => {
1440
+ const existing = entries.get(provider);
1441
+ if (existing) {
1442
+ return existing;
1443
+ }
1444
+ const entry = {
1445
+ elapsedCount: 0,
1446
+ elapsedTotal: 0,
1447
+ errorCount: 0,
1448
+ fallbackCount: 0,
1449
+ provider,
1450
+ rateLimited: false,
1451
+ recommended: false,
1452
+ runCount: 0,
1453
+ status: "idle",
1454
+ timeoutCount: 0
1455
+ };
1456
+ entries.set(provider, entry);
1457
+ return entry;
1458
+ };
1459
+ for (const provider of providers) {
1460
+ getEntry(provider);
1461
+ }
1462
+ const hasProviderRouterEvents = events.some((event) => event.type === "session.error" && isAllowedProvider(event.payload.provider) && isProviderStatus(event.payload.providerStatus));
1463
+ for (const event of events) {
1464
+ if (event.type === "assistant.run") {
1465
+ if (hasProviderRouterEvents) {
1466
+ continue;
1467
+ }
1468
+ const provider2 = event.payload.variantId;
1469
+ if (!isAllowedProvider(provider2)) {
1470
+ continue;
1471
+ }
1472
+ const entry2 = getEntry(provider2);
1473
+ entry2.runCount += 1;
1474
+ const elapsedMs = getNumber(event.payload.elapsedMs);
1475
+ if (elapsedMs !== undefined) {
1476
+ entry2.elapsedCount += 1;
1477
+ entry2.elapsedTotal += elapsedMs;
1478
+ }
1479
+ continue;
1480
+ }
1481
+ if (event.type !== "session.error") {
1482
+ continue;
1483
+ }
1484
+ const provider = event.payload.provider;
1485
+ if (!isAllowedProvider(provider)) {
1486
+ continue;
1487
+ }
1488
+ const providerStatus = isProviderStatus(event.payload.providerStatus) ? event.payload.providerStatus : undefined;
1489
+ const applyProviderHealth = () => {
1490
+ const entry2 = getEntry(provider);
1491
+ const providerHealth = event.payload.providerHealth;
1492
+ if (providerHealth && typeof providerHealth === "object") {
1493
+ const suppressedUntil2 = getNumber(providerHealth.suppressedUntil);
1494
+ if (suppressedUntil2 !== undefined) {
1495
+ entry2.suppressedUntil = suppressedUntil2;
1496
+ }
1497
+ }
1498
+ const suppressedUntil = getNumber(event.payload.suppressedUntil);
1499
+ if (suppressedUntil !== undefined) {
1500
+ entry2.suppressedUntil = suppressedUntil;
1501
+ }
1502
+ const suppressionRemainingMs = getNumber(event.payload.suppressionRemainingMs);
1503
+ if (suppressionRemainingMs !== undefined) {
1504
+ entry2.suppressionRemainingMs = suppressionRemainingMs;
1505
+ }
1506
+ return entry2;
1507
+ };
1508
+ if (providerStatus === "success" || providerStatus === "fallback") {
1509
+ const entry2 = applyProviderHealth();
1510
+ entry2.runCount += 1;
1511
+ entry2.lastSuccessAt = event.at;
1512
+ if (providerStatus === "success") {
1513
+ entry2.lastError = undefined;
1514
+ entry2.rateLimited = false;
1515
+ entry2.suppressedUntil = undefined;
1516
+ entry2.suppressionRemainingMs = undefined;
1517
+ }
1518
+ const elapsedMs = getNumber(event.payload.elapsedMs);
1519
+ if (elapsedMs !== undefined) {
1520
+ entry2.elapsedCount += 1;
1521
+ entry2.elapsedTotal += elapsedMs;
1522
+ }
1523
+ const selectedProvider = event.payload.selectedProvider;
1524
+ if (providerStatus === "fallback" && isAllowedProvider(selectedProvider) && selectedProvider !== provider) {
1525
+ getEntry(selectedProvider).fallbackCount += 1;
1526
+ }
1527
+ continue;
1528
+ }
1529
+ const entry = applyProviderHealth();
1530
+ entry.errorCount += 1;
1531
+ if (event.payload.timedOut === true) {
1532
+ entry.timeoutCount += 1;
1533
+ }
1534
+ entry.lastError = getString(event.payload.error);
1535
+ entry.lastErrorAt = event.at;
1536
+ entry.rateLimited ||= event.payload.rateLimited === true;
1537
+ }
1538
+ const summaries = [...entries.values()].map((entry) => {
1539
+ const hadSuppression = typeof entry.suppressedUntil === "number" || typeof entry.suppressionRemainingMs === "number";
1540
+ const suppressionRemainingMs = typeof entry.suppressedUntil === "number" ? Math.max(0, entry.suppressedUntil - now) : entry.suppressionRemainingMs;
1541
+ const activeSuppression = typeof suppressionRemainingMs === "number" && suppressionRemainingMs > 0;
1542
+ const recoverable = hadSuppression && !activeSuppression;
1543
+ const averageElapsedMs = entry.elapsedCount > 0 ? Math.round(entry.elapsedTotal / entry.elapsedCount) : undefined;
1544
+ const status = activeSuppression ? "suppressed" : recoverable ? "recoverable" : entry.rateLimited ? "rate-limited" : entry.errorCount > 0 && (!entry.lastSuccessAt || !entry.lastErrorAt || entry.lastErrorAt > entry.lastSuccessAt) ? "degraded" : entry.runCount > 0 ? "healthy" : "idle";
1545
+ return {
1546
+ averageElapsedMs,
1547
+ errorCount: entry.errorCount,
1548
+ fallbackCount: entry.fallbackCount,
1549
+ lastError: entry.lastError,
1550
+ lastErrorAt: entry.lastErrorAt,
1551
+ lastSuccessAt: entry.lastSuccessAt,
1552
+ provider: entry.provider,
1553
+ rateLimited: entry.rateLimited,
1554
+ recommended: false,
1555
+ runCount: entry.runCount,
1556
+ status,
1557
+ suppressionRemainingMs: activeSuppression ? suppressionRemainingMs : undefined,
1558
+ suppressedUntil: entry.suppressedUntil,
1559
+ timeoutCount: entry.timeoutCount
1560
+ };
1561
+ });
1562
+ const recommended = summaries.filter((entry) => entry.status === "healthy").sort((left, right) => (left.averageElapsedMs ?? Number.MAX_SAFE_INTEGER) - (right.averageElapsedMs ?? Number.MAX_SAFE_INTEGER))[0];
1563
+ if (recommended) {
1564
+ recommended.recommended = true;
1565
+ }
1566
+ return summaries;
1567
+ };
1568
+ var escapeHtml5 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1569
+ var renderVoiceProviderHealthHTML = (providers) => providers.length === 0 ? '<p class="voice-provider-empty">No provider status yet.</p>' : [
1570
+ '<div class="voice-provider-health">',
1571
+ ...providers.map((provider) => {
1572
+ const suppressionSeconds = typeof provider.suppressionRemainingMs === "number" ? Math.ceil(provider.suppressionRemainingMs / 1000) : undefined;
1573
+ return [
1574
+ `<article class="voice-provider-card ${escapeHtml5(provider.status)}">`,
1575
+ '<div class="voice-provider-card-header">',
1576
+ `<strong>${escapeHtml5(provider.provider)}</strong>`,
1577
+ `<span>${escapeHtml5(provider.status)}${provider.recommended ? " \xB7 recommended" : ""}</span>`,
1578
+ "</div>",
1579
+ "<dl>",
1580
+ `<div><dt>Runs</dt><dd>${String(provider.runCount)}</dd></div>`,
1581
+ `<div><dt>Avg latency</dt><dd>${String(provider.averageElapsedMs ?? 0)}ms</dd></div>`,
1582
+ `<div><dt>Errors</dt><dd>${String(provider.errorCount)}</dd></div>`,
1583
+ `<div><dt>Timeouts</dt><dd>${String(provider.timeoutCount)}</dd></div>`,
1584
+ `<div><dt>Fallbacks</dt><dd>${String(provider.fallbackCount)}</dd></div>`,
1585
+ "</dl>",
1586
+ suppressionSeconds ? `<p>Temporarily suppressed for ${String(suppressionSeconds)}s.</p>` : "",
1587
+ provider.lastError ? `<p>${escapeHtml5(provider.lastError)}</p>` : "",
1588
+ "</article>"
1589
+ ].join("");
1590
+ }),
1591
+ "</div>"
1592
+ ].join("");
1593
+ var createVoiceProviderHealthJSONHandler = (options) => async () => summarizeVoiceProviderHealth(options);
1594
+ var createVoiceProviderHealthHTMLHandler = (options) => async () => {
1595
+ const providers = await summarizeVoiceProviderHealth(options);
1596
+ const render = options.render ?? renderVoiceProviderHealthHTML;
1597
+ const body = await render(providers);
1598
+ return new Response(body, {
1599
+ headers: {
1600
+ "Content-Type": "text/html; charset=utf-8",
1601
+ ...options.headers
1602
+ }
1603
+ });
1604
+ };
1605
+ var createVoiceProviderHealthRoutes = (options) => {
1606
+ const path = options.path ?? "/api/provider-status";
1607
+ const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
1608
+ const routes = new Elysia({
1609
+ name: options.name ?? "absolutejs-voice-provider-health"
1610
+ }).get(path, createVoiceProviderHealthJSONHandler(options));
1611
+ if (htmlPath) {
1612
+ routes.get(htmlPath, createVoiceProviderHealthHTMLHandler(options));
1613
+ }
1614
+ return routes;
1615
+ };
1616
+
1617
+ // src/resilienceRoutes.ts
1618
+ var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1619
+ var getString2 = (value) => typeof value === "string" ? value : undefined;
1620
+ var getNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
1621
+ var getBoolean = (value) => value === true;
1622
+ var isProviderStatus2 = (value) => value === "error" || value === "fallback" || value === "success";
1623
+ var listVoiceRoutingEvents = (events) => {
1624
+ const routingEvents = [];
1625
+ for (const event of events) {
1626
+ if (event.type !== "session.error") {
1627
+ continue;
1628
+ }
1629
+ const provider = getString2(event.payload.provider);
1630
+ const providerStatus = isProviderStatus2(event.payload.providerStatus) ? event.payload.providerStatus : undefined;
1631
+ if (!provider || !providerStatus) {
1632
+ continue;
1633
+ }
1634
+ const kind = getString2(event.payload.kind);
1635
+ routingEvents.push({
1636
+ at: event.at,
1637
+ attempt: getNumber2(event.payload.attempt),
1638
+ elapsedMs: getNumber2(event.payload.elapsedMs),
1639
+ error: getString2(event.payload.error),
1640
+ fallbackProvider: getString2(event.payload.fallbackProvider),
1641
+ kind: kind === "stt" || kind === "tts" ? kind : "llm",
1642
+ latencyBudgetMs: getNumber2(event.payload.latencyBudgetMs),
1643
+ operation: getString2(event.payload.operation),
1644
+ provider,
1645
+ routing: getString2(event.payload.routing),
1646
+ scenarioId: event.scenarioId,
1647
+ selectedProvider: getString2(event.payload.selectedProvider),
1648
+ sessionId: event.sessionId,
1649
+ status: providerStatus,
1650
+ suppressionRemainingMs: getNumber2(event.payload.suppressionRemainingMs),
1651
+ timedOut: getBoolean(event.payload.timedOut),
1652
+ turnId: event.turnId
1653
+ });
1654
+ }
1655
+ return routingEvents.sort((left, right) => right.at - left.at);
1656
+ };
1657
+ var summarizeVoiceRoutingDecision = (events, options = {}) => {
1658
+ const routingEvents = listVoiceRoutingEvents(events).filter((event) => {
1659
+ if (options.kind && event.kind !== options.kind) {
1660
+ return false;
1661
+ }
1662
+ if (options.sessionId && event.sessionId !== options.sessionId) {
1663
+ return false;
1664
+ }
1665
+ return true;
1666
+ });
1667
+ const limited = typeof options.limit === "number" && options.limit >= 0 ? routingEvents.slice(0, options.limit) : routingEvents;
1668
+ return limited[0] ?? null;
1669
+ };
1670
+ var createEmptyKindSummary = () => ({
1671
+ errorCount: 0,
1672
+ fallbackCount: 0,
1673
+ providers: [],
1674
+ runCount: 0,
1675
+ timeoutCount: 0
1676
+ });
1677
+ var summarizeVoiceRoutingSessions = (events, options = {}) => {
1678
+ const routingEvents = (events.some((event) => ("payload" in event)) ? listVoiceRoutingEvents(events) : [...events]).filter((event) => !options.sessionId || event.sessionId === options.sessionId);
1679
+ const sessions = new Map;
1680
+ for (const event of routingEvents) {
1681
+ const existing = sessions.get(event.sessionId);
1682
+ const summary = existing ?? {
1683
+ errorCount: 0,
1684
+ eventCount: 0,
1685
+ fallbackCount: 0,
1686
+ kinds: {
1687
+ llm: createEmptyKindSummary(),
1688
+ stt: createEmptyKindSummary(),
1689
+ tts: createEmptyKindSummary()
1690
+ },
1691
+ lastEventAt: event.at,
1692
+ sessionId: event.sessionId,
1693
+ startedAt: event.at,
1694
+ status: "healthy",
1695
+ timeoutCount: 0
1696
+ };
1697
+ summary.eventCount += 1;
1698
+ summary.startedAt = Math.min(summary.startedAt, event.at);
1699
+ summary.lastEventAt = Math.max(summary.lastEventAt, event.at);
1700
+ if (event.status === "error") {
1701
+ summary.errorCount += 1;
1702
+ }
1703
+ if (event.status === "fallback") {
1704
+ summary.fallbackCount += 1;
1705
+ }
1706
+ if (event.timedOut) {
1707
+ summary.timeoutCount += 1;
1708
+ }
1709
+ const kind = summary.kinds[event.kind];
1710
+ kind.runCount += 1;
1711
+ if (event.status === "error") {
1712
+ kind.errorCount += 1;
1713
+ }
1714
+ if (event.status === "fallback") {
1715
+ kind.fallbackCount += 1;
1716
+ }
1717
+ if (event.timedOut) {
1718
+ kind.timeoutCount += 1;
1719
+ }
1720
+ if (event.provider && !kind.providers.includes(event.provider)) {
1721
+ kind.providers.push(event.provider);
1722
+ }
1723
+ if (!kind.latest || event.at > kind.latest.at) {
1724
+ kind.latest = event;
1725
+ }
1726
+ summary.status = summary.errorCount > 0 || summary.timeoutCount > 0 ? "degraded" : summary.fallbackCount > 0 ? "fallback" : "healthy";
1727
+ sessions.set(event.sessionId, summary);
1728
+ }
1729
+ const sorted = [...sessions.values()].sort((left, right) => right.lastEventAt - left.lastEventAt);
1730
+ return typeof options.limit === "number" && options.limit >= 0 ? sorted.slice(0, options.limit) : sorted;
1731
+ };
1732
+ var createVoiceRoutingDecisionSummary = async (options) => {
1733
+ const events = await options.store.list({
1734
+ sessionId: options.sessionId,
1735
+ type: "session.error"
1736
+ });
1737
+ return summarizeVoiceRoutingDecision(events, options);
1738
+ };
1739
+ var summarizeRoutingEvents = (events) => {
1740
+ const byKind = new Map;
1741
+ let errors = 0;
1742
+ let fallbacks = 0;
1743
+ let timeouts = 0;
1744
+ for (const event of events) {
1745
+ byKind.set(event.kind, (byKind.get(event.kind) ?? 0) + 1);
1746
+ if (event.status === "error") {
1747
+ errors += 1;
1748
+ }
1749
+ if (event.status === "fallback") {
1750
+ fallbacks += 1;
1751
+ }
1752
+ if (event.timedOut) {
1753
+ timeouts += 1;
1754
+ }
1755
+ }
1756
+ return {
1757
+ byKind,
1758
+ errors,
1759
+ fallbacks,
1760
+ timeouts,
1761
+ total: events.length
1762
+ };
1763
+ };
1764
+ var renderProviderCards = (title, providers) => {
1765
+ if (providers.length === 0) {
1766
+ return `<p class="muted">No ${escapeHtml6(title)} provider health yet.</p>`;
1767
+ }
1768
+ return `<div class="provider-grid">${providers.map((provider) => `
1769
+ <article class="card provider ${escapeHtml6(provider.status)}">
1770
+ <div class="card-header">
1771
+ <strong>${escapeHtml6(provider.provider)}</strong>
1772
+ <span>${escapeHtml6(provider.status)}${provider.recommended ? " \xB7 recommended" : ""}</span>
1773
+ </div>
1774
+ <dl>
1775
+ <div><dt>Runs</dt><dd>${provider.runCount}</dd></div>
1776
+ <div><dt>Avg latency</dt><dd>${provider.averageElapsedMs ?? 0}ms</dd></div>
1777
+ <div><dt>Errors</dt><dd>${provider.errorCount}</dd></div>
1778
+ <div><dt>Timeouts</dt><dd>${provider.timeoutCount}</dd></div>
1779
+ <div><dt>Fallbacks</dt><dd>${provider.fallbackCount}</dd></div>
1780
+ </dl>
1781
+ ${provider.lastError ? `<p class="muted">${escapeHtml6(provider.lastError)}</p>` : ""}
1782
+ </article>
1783
+ `).join("")}</div>`;
1784
+ };
1785
+ var renderTimeline = (events) => {
1786
+ if (events.length === 0) {
1787
+ return '<p class="muted">No provider routing events yet. Run the app or simulate provider failover.</p>';
1788
+ }
1789
+ return `<div class="timeline">${events.slice(0, 40).map((event) => `
1790
+ <article class="card event ${escapeHtml6(event.status ?? "unknown")}">
1791
+ <div class="card-header">
1792
+ <strong>${escapeHtml6(event.kind.toUpperCase())} ${escapeHtml6(event.operation ?? "generate")}</strong>
1793
+ <span>${new Date(event.at).toLocaleString()}</span>
1794
+ </div>
1795
+ <p>
1796
+ <span class="pill">${escapeHtml6(event.status ?? "unknown")}</span>
1797
+ <span class="pill">provider: ${escapeHtml6(event.provider ?? "unknown")}</span>
1798
+ ${event.fallbackProvider ? `<span class="pill">fallback: ${escapeHtml6(event.fallbackProvider)}</span>` : ""}
1799
+ ${event.timedOut ? '<span class="pill danger">timed out</span>' : ""}
1800
+ </p>
1801
+ <dl>
1802
+ <div><dt>Attempt</dt><dd>${event.attempt ?? 0}</dd></div>
1803
+ <div><dt>Elapsed</dt><dd>${event.elapsedMs ?? 0}ms</dd></div>
1804
+ <div><dt>Budget</dt><dd>${event.latencyBudgetMs ?? 0}ms</dd></div>
1805
+ <div><dt>Session</dt><dd>${escapeHtml6(event.sessionId)}</dd></div>
1806
+ </dl>
1807
+ ${event.error ? `<p class="muted">${escapeHtml6(event.error)}</p>` : ""}
1808
+ </article>
1809
+ `).join("")}</div>`;
1810
+ };
1811
+ var renderSessionKind = (kind, summary) => {
1812
+ const latest = summary.latest;
1813
+ const provider = latest?.provider ?? summary.providers[0] ?? "none";
1814
+ const status = latest?.status ?? "idle";
1815
+ const fallback = latest?.fallbackProvider && latest.fallbackProvider !== provider ? ` -> ${latest.fallbackProvider}` : "";
1816
+ return `<div>
1817
+ <dt>${escapeHtml6(kind.toUpperCase())}</dt>
1818
+ <dd>${escapeHtml6(provider)}${escapeHtml6(fallback)}</dd>
1819
+ <small>${escapeHtml6(status)} \xB7 ${summary.runCount} event${summary.runCount === 1 ? "" : "s"} \xB7 ${summary.errorCount} error${summary.errorCount === 1 ? "" : "s"} \xB7 ${summary.fallbackCount} fallback${summary.fallbackCount === 1 ? "" : "s"}</small>
1820
+ </div>`;
1821
+ };
1822
+ var renderSessionSummaries = (sessions) => {
1823
+ if (sessions.length === 0) {
1824
+ return '<p class="muted">No call-level routing summaries yet. Run a voice session or provider simulation.</p>';
1825
+ }
1826
+ return `<div class="session-grid">${sessions.slice(0, 12).map((session) => `
1827
+ <article class="card session ${escapeHtml6(session.status)}">
1828
+ <div class="card-header">
1829
+ <strong>${escapeHtml6(session.sessionId)}</strong>
1830
+ <span>${escapeHtml6(session.status)}</span>
1831
+ </div>
1832
+ <p>
1833
+ <span class="pill">${session.eventCount} routing events</span>
1834
+ <span class="pill">${session.fallbackCount} fallbacks</span>
1835
+ <span class="pill">${session.errorCount} errors</span>
1836
+ <span class="pill">${session.timeoutCount} timeouts</span>
1837
+ </p>
1838
+ <dl>
1839
+ ${renderSessionKind("llm", session.kinds.llm)}
1840
+ ${renderSessionKind("stt", session.kinds.stt)}
1841
+ ${renderSessionKind("tts", session.kinds.tts)}
1842
+ </dl>
1843
+ </article>
1844
+ `).join("")}</div>`;
1845
+ };
1846
+ var renderSimulationControls = (kind, simulation) => {
1847
+ if (!simulation) {
1848
+ return "";
1849
+ }
1850
+ const configuredProviders = simulation.providers.filter((provider) => provider.configured !== false);
1851
+ if (configuredProviders.length === 0) {
1852
+ return `<p class="muted">No ${kind.toUpperCase()} providers are configured for simulation.</p>`;
1853
+ }
1854
+ const pathPrefix = simulation.pathPrefix ?? `/api/${kind}-simulate`;
1855
+ const failureProviders = simulation.failureProviders ?? configuredProviders.map(({ provider }) => provider);
1856
+ const canFail = (provider) => configuredProviders.some((entry) => entry.provider === provider) && (!simulation.fallbackRequiredProvider || configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider));
1857
+ return `<div class="simulate-panel" data-sim-kind="${kind}" data-sim-prefix="${escapeHtml6(pathPrefix)}">
1858
+ <p class="muted">${escapeHtml6(simulation.failureMessage ?? `Simulate ${kind.toUpperCase()} provider failure without changing provider credentials.`)}</p>
1859
+ <div class="simulate-actions">
1860
+ ${failureProviders.map((provider) => `<button type="button" data-provider-fail="${escapeHtml6(provider)}"${canFail(provider) ? "" : " disabled"}>Simulate ${escapeHtml6(provider)} ${kind.toUpperCase()} failure</button>`).join("")}
1861
+ ${configuredProviders.map((provider) => `<button type="button" data-provider-recover="${escapeHtml6(provider.provider)}">Mark ${escapeHtml6(provider.provider)} recovered</button>`).join("")}
1862
+ </div>
1863
+ ${simulation.fallbackRequiredProvider && !configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider) ? `<p class="muted">${escapeHtml6(simulation.fallbackRequiredMessage ?? `Configure ${simulation.fallbackRequiredProvider} to enable fallback simulation.`)}</p>` : ""}
1864
+ <pre class="simulate-output" hidden></pre>
1865
+ </div>`;
1866
+ };
1867
+ var renderVoiceResilienceHTML = (input) => {
1868
+ const summary = summarizeRoutingEvents(input.routingEvents);
1869
+ const kindCounts = [...summary.byKind.entries()].map(([kind, count]) => `<span class="pill">${escapeHtml6(kind)}: ${String(count)}</span>`).join("");
1870
+ const links = input.links?.length ? input.links.map((link) => `<a href="${escapeHtml6(link.href)}">${escapeHtml6(link.label)}</a>`).join(" \xB7 ") : "";
1871
+ const snippet = escapeHtml6(`const sttSimulator = createVoiceIOProviderFailureSimulator({
1872
+ kind: 'stt',
1873
+ providers: ['deepgram', 'assemblyai'],
1874
+ fallback: ['deepgram', 'assemblyai'],
1875
+ onProviderEvent: async (event, input) => {
1876
+ await traceStore.append({
1877
+ at: event.at,
1878
+ payload: { ...event, providerStatus: event.status },
1879
+ sessionId: input.sessionId,
1880
+ type: 'session.error'
1881
+ });
1882
+ }
1883
+ });
1884
+
1885
+ app.use(
1886
+ createVoiceResilienceRoutes({
1887
+ store: traceStore,
1888
+ sttProviders: ['deepgram', 'assemblyai'],
1889
+ sttSimulation: {
1890
+ failureProviders: ['deepgram'],
1891
+ fallbackRequiredProvider: 'assemblyai',
1892
+ providers: [{ provider: 'deepgram' }, { provider: 'assemblyai' }],
1893
+ run: sttSimulator.run
1894
+ }
1895
+ })
1896
+ );
1897
+
1898
+ app.use(
1899
+ createVoiceProductionReadinessRoutes({
1900
+ links: { resilience: '/resilience' },
1901
+ store: traceStore
1902
+ })
1903
+ );`);
1904
+ return `<!doctype html>
1905
+ <html lang="en">
1906
+ <head>
1907
+ <meta charset="utf-8" />
1908
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
1909
+ <title>${escapeHtml6(input.title ?? "AbsoluteJS Voice Resilience")}</title>
1910
+ <style>
1911
+ :root { color-scheme: dark; }
1912
+ body { background: radial-gradient(circle at top left, #172554, #09090b 36%, #050505); color: #f4f4f5; font-family: ui-sans-serif, system-ui, sans-serif; margin: 0; padding: 24px; }
1913
+ main { display: grid; gap: 16px; margin: 0 auto; max-width: 1180px; }
1914
+ section, .card { background: rgba(19, 22, 27, 0.92); border: 1px solid #27272a; border-radius: 20px; padding: 20px; }
1915
+ .hero { background: linear-gradient(135deg, rgba(14, 165, 233, 0.18), rgba(245, 158, 11, 0.12)); }
1916
+ .grid, .provider-grid { display: grid; gap: 14px; grid-template-columns: repeat(4, minmax(0, 1fr)); }
1917
+ .session-grid { display: grid; gap: 14px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
1918
+ .timeline { display: grid; gap: 12px; }
1919
+ .card-header { align-items: center; display: flex; gap: 12px; justify-content: space-between; }
1920
+ .card-header strong { font-size: 1.05rem; }
1921
+ .metric strong { display: block; font-size: 2rem; margin-top: 6px; }
1922
+ .muted, dt, span { color: #a1a1aa; }
1923
+ dl { display: grid; gap: 8px; grid-template-columns: repeat(4, minmax(0, 1fr)); }
1924
+ dl div { background: #0f1217; border: 1px solid #27272a; border-radius: 12px; padding: 10px; }
1925
+ dd { font-weight: 800; margin: 4px 0 0; }
1926
+ .pill { background: #0f1217; border: 1px solid #3f3f46; border-radius: 999px; color: #d4d4d8; display: inline-flex; margin: 3px 4px 3px 0; padding: 5px 9px; }
1927
+ .danger { border-color: rgba(239, 68, 68, 0.75); color: #fecaca; }
1928
+ .event.error { border-color: rgba(239, 68, 68, 0.7); }
1929
+ .event.fallback, .session.fallback { border-color: rgba(245, 158, 11, 0.7); }
1930
+ .event.success, .provider.healthy, .session.healthy { border-color: rgba(34, 197, 94, 0.5); }
1931
+ .session.degraded { border-color: rgba(239, 68, 68, 0.7); }
1932
+ .provider.suppressed, .provider.degraded, .provider.rate-limited { border-color: rgba(239, 68, 68, 0.7); }
1933
+ .provider.recoverable { border-color: rgba(59, 130, 246, 0.7); }
1934
+ button { background: #f59e0b; border: 0; border-radius: 999px; color: #111827; cursor: pointer; font-weight: 800; padding: 10px 14px; }
1935
+ button:disabled { cursor: not-allowed; opacity: 0.45; }
1936
+ .simulate-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
1937
+ .simulate-output { background: #050505; border: 1px solid #27272a; border-radius: 14px; color: #d4d4d8; overflow: auto; padding: 12px; white-space: pre-wrap; }
1938
+ .primitive { border-color: rgba(245, 158, 11, 0.45); }
1939
+ .primitive pre { background: #050505; border: 1px solid #27272a; border-radius: 14px; color: #fef3c7; overflow: auto; padding: 14px; }
1940
+ .primitive code { color: #fef3c7; }
1941
+ a { color: #f59e0b; }
1942
+ @media (max-width: 850px) { .grid, .provider-grid, .session-grid, dl { grid-template-columns: 1fr; } }
1943
+ </style>
1944
+ </head>
1945
+ <body>
1946
+ <main>
1947
+ <section class="hero">
1948
+ <h1>Provider routing and resilience</h1>
1949
+ <p>One view for the production reliability story: LLM failover, STT/TTS routing, latency budgets, timeouts, and fallback decisions.</p>
1950
+ ${links ? `<p>${links}</p>` : ""}
1951
+ <p>${kindCounts || '<span class="pill">No routing events yet</span>'}</p>
1952
+ </section>
1953
+ <section class="primitive">
1954
+ <p class="muted">Copy into your app</p>
1955
+ <h2><code>createVoiceResilienceRoutes(...)</code> builds this failover proof surface</h2>
1956
+ <p class="muted">Mount one route group for provider health, routing traces, and failure simulation. Feed the same trace store into production readiness so unresolved provider errors fail the deploy gate while recovered fallback stays visible.</p>
1957
+ <pre><code>${snippet}</code></pre>
1958
+ </section>
1959
+ <section class="grid">
1960
+ <article class="card metric"><span>Total routing events</span><strong>${summary.total}</strong></article>
1961
+ <article class="card metric"><span>Fallbacks</span><strong>${summary.fallbacks}</strong></article>
1962
+ <article class="card metric"><span>Errors</span><strong>${summary.errors}</strong></article>
1963
+ <article class="card metric"><span>Timeouts</span><strong>${summary.timeouts}</strong></article>
1964
+ </section>
1965
+ <section>
1966
+ <h2>Call-level routing summaries</h2>
1967
+ <p class="muted">A compact per-call view of which LLM, STT, and TTS providers handled the session, including fallback and timeout counts.</p>
1968
+ ${renderSessionSummaries(input.routingSessions)}
1969
+ </section>
1970
+ <section>
1971
+ <h2>LLM provider health</h2>
1972
+ ${renderProviderCards("LLM", input.llmProviderHealth)}
1973
+ </section>
1974
+ <section>
1975
+ <h2>STT provider health</h2>
1976
+ ${renderSimulationControls("stt", input.sttSimulation)}
1977
+ ${renderProviderCards("STT", input.sttProviderHealth)}
1978
+ </section>
1979
+ <section>
1980
+ <h2>TTS provider health</h2>
1981
+ ${renderSimulationControls("tts", input.ttsSimulation)}
1982
+ ${renderProviderCards("TTS", input.ttsProviderHealth)}
1983
+ </section>
1984
+ <section>
1985
+ <h2>Routing timeline</h2>
1986
+ ${renderTimeline(input.routingEvents)}
1987
+ </section>
1988
+ </main>
1989
+ <script>
1990
+ const showResult = (panel, result) => {
1991
+ const output = panel.querySelector(".simulate-output");
1992
+ if (!output) return;
1993
+ output.hidden = false;
1994
+ output.textContent = JSON.stringify(result, null, 2);
1995
+ };
1996
+ document.querySelectorAll("[data-sim-prefix]").forEach((panel) => {
1997
+ const prefix = panel.getAttribute("data-sim-prefix");
1998
+ panel.querySelectorAll("[data-provider-fail]").forEach((button) => {
1999
+ button.addEventListener("click", async () => {
2000
+ const provider = button.getAttribute("data-provider-fail");
2001
+ const response = await fetch(prefix + "/failure?provider=" + encodeURIComponent(provider || ""), { method: "POST" });
2002
+ showResult(panel, await response.json());
2003
+ if (response.ok) window.setTimeout(() => window.location.reload(), 450);
2004
+ });
2005
+ });
2006
+ panel.querySelectorAll("[data-provider-recover]").forEach((button) => {
2007
+ button.addEventListener("click", async () => {
2008
+ const provider = button.getAttribute("data-provider-recover");
2009
+ const response = await fetch(prefix + "/recovery?provider=" + encodeURIComponent(provider || ""), { method: "POST" });
2010
+ showResult(panel, await response.json());
2011
+ if (response.ok) window.setTimeout(() => window.location.reload(), 450);
2012
+ });
2013
+ });
2014
+ });
2015
+ </script>
2016
+ </body>
2017
+ </html>`;
2018
+ };
2019
+ var providerFromQuery = (value, providers) => typeof value === "string" && providers.some((provider) => provider.provider === value && provider.configured !== false) ? value : undefined;
2020
+ var registerSimulationRoutes = (routes, simulation, defaultPathPrefix) => {
2021
+ if (!simulation) {
2022
+ return routes;
2023
+ }
2024
+ const pathPrefix = simulation.pathPrefix ?? defaultPathPrefix;
2025
+ routes.post(`${pathPrefix}/failure`, async ({ query, set }) => {
2026
+ const provider = providerFromQuery(query.provider, simulation.providers);
2027
+ if (!provider) {
2028
+ set.status = 400;
2029
+ return {
2030
+ error: "Provider is not configured for simulation."
2031
+ };
2032
+ }
2033
+ if (simulation.failureProviders && !simulation.failureProviders.includes(provider)) {
2034
+ set.status = 400;
2035
+ return {
2036
+ error: `${provider} is not configured for failure simulation.`
2037
+ };
2038
+ }
2039
+ if (simulation.fallbackRequiredProvider && !simulation.providers.some((entry) => entry.provider === simulation.fallbackRequiredProvider && entry.configured !== false)) {
2040
+ set.status = 400;
2041
+ return {
2042
+ error: simulation.fallbackRequiredMessage ?? `Configure ${simulation.fallbackRequiredProvider} before simulating fallback.`
2043
+ };
2044
+ }
2045
+ return simulation.run(provider, "failure");
2046
+ });
2047
+ routes.post(`${pathPrefix}/recovery`, async ({ query, set }) => {
2048
+ const provider = providerFromQuery(query.provider, simulation.providers);
2049
+ if (!provider) {
2050
+ set.status = 400;
2051
+ return {
2052
+ error: "Provider is not configured for simulation."
2053
+ };
2054
+ }
2055
+ return simulation.run(provider, "recovery");
2056
+ });
2057
+ return routes;
2058
+ };
2059
+ var createVoiceResilienceRoutes = (options) => {
2060
+ const path = options.path ?? "/resilience";
2061
+ const routes = new Elysia2({
2062
+ name: options.name ?? "absolutejs-voice-resilience"
2063
+ }).get(path, async () => {
2064
+ const events = await options.store.list();
2065
+ const sttEvents = events.filter((event) => event.payload.kind === "stt");
2066
+ const ttsEvents = events.filter((event) => event.payload.kind === "tts");
2067
+ const routingEvents = listVoiceRoutingEvents(events);
2068
+ const data = {
2069
+ links: options.links,
2070
+ llmProviderHealth: await summarizeVoiceProviderHealth({
2071
+ events,
2072
+ providers: options.llmProviders ?? []
2073
+ }),
2074
+ routingEvents,
2075
+ routingSessions: summarizeVoiceRoutingSessions(routingEvents),
2076
+ sttProviderHealth: await summarizeVoiceProviderHealth({
2077
+ events: sttEvents,
2078
+ providers: options.sttProviders ?? []
2079
+ }),
2080
+ sttSimulation: options.sttSimulation,
2081
+ title: options.title,
2082
+ ttsProviderHealth: await summarizeVoiceProviderHealth({
2083
+ events: ttsEvents,
2084
+ providers: options.ttsProviders ?? []
2085
+ }),
2086
+ ttsSimulation: options.ttsSimulation
2087
+ };
2088
+ const body = await (options.render ?? renderVoiceResilienceHTML)(data);
2089
+ return new Response(body, {
2090
+ headers: {
2091
+ "Content-Type": "text/html; charset=utf-8",
2092
+ ...options.headers
2093
+ }
2094
+ });
2095
+ });
2096
+ registerSimulationRoutes(routes, options.sttSimulation, "/api/stt-simulate");
2097
+ registerSimulationRoutes(routes, options.ttsSimulation, "/api/tts-simulate");
2098
+ return routes;
2099
+ };
2100
+
2101
+ // src/providerDecisionTraces.ts
2102
+ var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2103
+ var getString3 = (value) => typeof value === "string" ? value : undefined;
2104
+ var getNumber3 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
2105
+ var isDecisionTrace = (event) => Boolean(event && typeof event === "object" && "provider" in event && "reason" in event && "sessionId" in event && "status" in event && "surface" in event);
2106
+ var surfaceForKind = (kind) => {
2107
+ switch (kind) {
2108
+ case "stt":
2109
+ return "live-stt";
2110
+ case "tts":
2111
+ return "telephony-tts";
2112
+ case "llm":
2113
+ default:
2114
+ return "live-call";
2115
+ }
2116
+ };
2117
+ var statusRank = { fail: 2, pass: 0, warn: 1 };
2118
+ var reportStatus = (issues) => issues.reduce((status, issue) => statusRank[issue.status] > statusRank[status] ? issue.status : status, "pass");
2119
+ var uniqueSorted = (values) => [
2120
+ ...new Set(values.filter((value) => typeof value === "string"))
2121
+ ].sort();
2122
+ var createVoiceProviderDecisionTraceEvent = (input) => {
2123
+ const surface = input.surface ?? surfaceForKind(input.kind);
2124
+ const reason = input.reason ?? (input.status === "degraded" ? `Provider ${input.provider} degraded to ${input.fallbackProvider ?? input.selectedProvider ?? "lower-fidelity fallback"}.` : input.status === "fallback" ? `Fallback from ${input.provider} to ${input.fallbackProvider ?? input.selectedProvider ?? "next provider"}.` : input.status === "error" ? `Provider ${input.provider} errored before recovery.` : input.status === "skipped" ? `Provider ${input.provider} was skipped by policy.` : `Provider ${input.selectedProvider ?? input.provider} selected by policy.`);
2125
+ return {
2126
+ at: input.at ?? Date.now(),
2127
+ payload: {
2128
+ ...input,
2129
+ providerDecision: true,
2130
+ reason,
2131
+ surface
2132
+ },
2133
+ scenarioId: input.scenarioId,
2134
+ sessionId: input.sessionId ?? `${surface}-provider-decision`,
2135
+ turnId: input.turnId,
2136
+ type: "provider.decision"
2137
+ };
2138
+ };
2139
+ var listVoiceProviderDecisionTraces = (events) => {
2140
+ if (events.every(isDecisionTrace)) {
2141
+ return [...events].sort((left, right) => right.at - left.at);
2142
+ }
2143
+ const traceEvents = events;
2144
+ const explicit = traceEvents.filter((event) => event.type === "provider.decision").map((event) => {
2145
+ const provider = getString3(event.payload.provider);
2146
+ const status = getString3(event.payload.status);
2147
+ const surface = getString3(event.payload.surface);
2148
+ if (!provider || !surface || status !== "error" && status !== "fallback" && status !== "degraded" && status !== "selected" && status !== "skipped" && status !== "success") {
2149
+ return;
2150
+ }
2151
+ return {
2152
+ at: event.at,
2153
+ elapsedMs: getNumber3(event.payload.elapsedMs),
2154
+ error: getString3(event.payload.error),
2155
+ fallbackProvider: getString3(event.payload.fallbackProvider),
2156
+ kind: event.payload.kind === "llm" || event.payload.kind === "stt" || event.payload.kind === "tts" ? event.payload.kind : undefined,
2157
+ latencyBudgetMs: getNumber3(event.payload.latencyBudgetMs),
2158
+ provider,
2159
+ reason: getString3(event.payload.reason) ?? `Provider ${provider} emitted ${status}.`,
2160
+ scenarioId: event.scenarioId,
2161
+ selectedProvider: getString3(event.payload.selectedProvider),
2162
+ sessionId: event.sessionId,
2163
+ status,
2164
+ surface,
2165
+ turnId: event.turnId
2166
+ };
2167
+ }).filter((event) => Boolean(event));
2168
+ const routing = listVoiceRoutingEvents(traceEvents).map((event) => ({
2169
+ at: event.at,
2170
+ elapsedMs: event.elapsedMs,
2171
+ error: event.error,
2172
+ fallbackProvider: event.fallbackProvider,
2173
+ kind: event.kind,
2174
+ latencyBudgetMs: event.latencyBudgetMs,
2175
+ provider: event.provider ?? event.selectedProvider ?? "unknown",
2176
+ reason: event.status === "fallback" ? `Fallback selected ${event.selectedProvider ?? event.fallbackProvider ?? "next provider"} after ${event.provider ?? "provider"} failed.` : event.status === "error" ? `Provider ${event.provider ?? "unknown"} errored before fallback recovery.` : `Provider ${event.selectedProvider ?? event.provider ?? "unknown"} completed successfully.`,
2177
+ scenarioId: event.scenarioId,
2178
+ selectedProvider: event.selectedProvider,
2179
+ sessionId: event.sessionId,
2180
+ status: event.status === "fallback" || event.status === "error" ? event.status : "success",
2181
+ surface: getString3(event.surface) ?? surfaceForKind(event.kind),
2182
+ turnId: event.turnId
2183
+ }));
2184
+ return [...explicit, ...routing].sort((left, right) => right.at - left.at);
2185
+ };
2186
+ var buildVoiceProviderDecisionTraceReport = async (options) => {
2187
+ const now = options.now ?? Date.now();
2188
+ const rawEvents = options.events ?? await options.store?.list() ?? [];
2189
+ const decisions = listVoiceProviderDecisionTraces(rawEvents).filter((decision) => {
2190
+ if (options.sessionId && decision.sessionId !== options.sessionId) {
2191
+ return false;
2192
+ }
2193
+ if (options.maxAgeMs !== undefined && now - decision.at > options.maxAgeMs) {
2194
+ return false;
2195
+ }
2196
+ return true;
2197
+ });
2198
+ const surfaces = new Map;
2199
+ const issues = [];
2200
+ for (const decision of decisions) {
2201
+ const group = surfaces.get(decision.surface) ?? [];
2202
+ group.push(decision);
2203
+ surfaces.set(decision.surface, group);
2204
+ }
2205
+ for (const surface of options.requiredSurfaces ?? []) {
2206
+ if (!surfaces.has(surface)) {
2207
+ issues.push({
2208
+ code: "voice.provider_decision_trace.surface_missing",
2209
+ message: `Surface ${surface} has no provider decision traces.`,
2210
+ status: "fail",
2211
+ surface
2212
+ });
2213
+ }
2214
+ }
2215
+ const fallbackCount = decisions.filter((decision) => decision.status === "fallback").length;
2216
+ const degradedCount = decisions.filter((decision) => decision.status === "degraded").length;
2217
+ const statuses = new Set(decisions.map((decision) => decision.status));
2218
+ const providers = uniqueSorted(decisions.flatMap((decision) => [
2219
+ decision.provider,
2220
+ decision.selectedProvider,
2221
+ decision.fallbackProvider
2222
+ ]));
2223
+ const fallbackProviders = uniqueSorted(decisions.flatMap((decision) => [
2224
+ decision.fallbackProvider,
2225
+ decision.status === "fallback" || decision.status === "degraded" ? decision.selectedProvider : undefined
2226
+ ]));
2227
+ if (options.minDecisions !== undefined && decisions.length < options.minDecisions) {
2228
+ issues.push({
2229
+ code: "voice.provider_decision_trace.min_decisions",
2230
+ message: `Found ${String(decisions.length)} provider decision trace(s); expected at least ${String(options.minDecisions)}.`,
2231
+ status: "fail"
2232
+ });
2233
+ }
2234
+ if (options.minFallbacks !== undefined && fallbackCount < options.minFallbacks) {
2235
+ issues.push({
2236
+ code: "voice.provider_decision_trace.min_fallbacks",
2237
+ message: `Found ${String(fallbackCount)} provider fallback trace(s); expected at least ${String(options.minFallbacks)}.`,
2238
+ status: "fail"
2239
+ });
2240
+ }
2241
+ if (options.minDegraded !== undefined && degradedCount < options.minDegraded) {
2242
+ issues.push({
2243
+ code: "voice.provider_decision_trace.min_degraded",
2244
+ message: `Found ${String(degradedCount)} provider degradation trace(s); expected at least ${String(options.minDegraded)}.`,
2245
+ status: "fail"
2246
+ });
2247
+ }
2248
+ for (const status of options.requiredStatuses ?? []) {
2249
+ if (!statuses.has(status)) {
2250
+ issues.push({
2251
+ code: "voice.provider_decision_trace.status_missing",
2252
+ message: `Missing provider decision status: ${status}.`,
2253
+ status: "fail"
2254
+ });
2255
+ }
2256
+ }
2257
+ for (const provider of options.requiredProviders ?? []) {
2258
+ if (!providers.includes(provider)) {
2259
+ issues.push({
2260
+ code: "voice.provider_decision_trace.provider_missing",
2261
+ message: `Missing provider decision provider: ${provider}.`,
2262
+ status: "fail"
2263
+ });
2264
+ }
2265
+ }
2266
+ for (const provider of options.requiredFallbackProviders ?? []) {
2267
+ if (!fallbackProviders.includes(provider)) {
2268
+ issues.push({
2269
+ code: "voice.provider_decision_trace.fallback_provider_missing",
2270
+ message: `Missing provider decision fallback provider: ${provider}.`,
2271
+ status: "fail"
2272
+ });
2273
+ }
2274
+ }
2275
+ for (const phrase of options.requiredReasonIncludes ?? []) {
2276
+ if (!decisions.some((decision) => decision.reason.includes(phrase))) {
2277
+ issues.push({
2278
+ code: "voice.provider_decision_trace.reason_missing",
2279
+ message: `Missing provider decision reason containing: ${phrase}.`,
2280
+ status: "fail"
2281
+ });
2282
+ }
2283
+ }
2284
+ const surfaceReports = [...surfaces.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([surface, surfaceDecisions]) => {
2285
+ const surfaceIssues = issues.filter((issue) => issue.surface === surface);
2286
+ return {
2287
+ degraded: surfaceDecisions.filter((decision) => decision.status === "degraded").length,
2288
+ decisions: surfaceDecisions.length,
2289
+ errors: surfaceDecisions.filter((decision) => decision.status === "error").length,
2290
+ fallbacks: surfaceDecisions.filter((decision) => decision.status === "fallback").length,
2291
+ issues: surfaceIssues,
2292
+ latestAt: Math.max(...surfaceDecisions.map((decision) => decision.at)),
2293
+ providers: uniqueSorted(surfaceDecisions.flatMap((decision) => [
2294
+ decision.provider,
2295
+ decision.selectedProvider,
2296
+ decision.fallbackProvider
2297
+ ])),
2298
+ reasons: uniqueSorted(surfaceDecisions.map((decision) => decision.reason)),
2299
+ selected: surfaceDecisions.filter((decision) => decision.status === "selected" || decision.status === "success").length,
2300
+ status: reportStatus(surfaceIssues),
2301
+ surface
2302
+ };
2303
+ });
2304
+ return {
2305
+ checkedAt: now,
2306
+ decisions,
2307
+ issues,
2308
+ status: reportStatus(issues),
2309
+ summary: {
2310
+ degraded: degradedCount,
2311
+ decisions: decisions.length,
2312
+ errors: decisions.filter((decision) => decision.status === "error").length,
2313
+ fallbacks: fallbackCount,
2314
+ providers: providers.length,
2315
+ selected: decisions.filter((decision) => decision.status === "selected" || decision.status === "success").length,
2316
+ surfaces: surfaces.size
2317
+ },
2318
+ surfaces: surfaceReports
2319
+ };
2320
+ };
2321
+ var renderVoiceProviderDecisionTraceMarkdown = (report) => [
2322
+ "# Voice Provider Decision Traces",
2323
+ "",
2324
+ `Status: **${report.status}**`,
2325
+ `Decisions: ${String(report.summary.decisions)}`,
2326
+ `Providers: ${String(report.summary.providers)}`,
2327
+ `Fallbacks: ${String(report.summary.fallbacks)}`,
2328
+ `Degraded: ${String(report.summary.degraded)}`,
2329
+ `Errors: ${String(report.summary.errors)}`,
2330
+ "",
2331
+ "| Surface | Status | Decisions | Selected | Fallbacks | Degraded | Errors | Providers |",
2332
+ "| --- | --- | ---: | ---: | ---: | ---: | ---: | --- |",
2333
+ ...report.surfaces.map((surface) => `| ${surface.surface} | ${surface.status} | ${String(surface.decisions)} | ${String(surface.selected)} | ${String(surface.fallbacks)} | ${String(surface.degraded)} | ${String(surface.errors)} | ${surface.providers.join(", ")} |`),
2334
+ "",
2335
+ ...report.issues.map((issue) => `- ${issue.status}: ${issue.message}`)
2336
+ ].join(`
2337
+ `);
2338
+ var renderVoiceProviderDecisionTraceHTML = (report, title = "Provider Decision Traces") => `<!doctype html>
2339
+ <html lang="en">
2340
+ <head>
2341
+ <meta charset="utf-8" />
2342
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
2343
+ <title>${escapeHtml7(title)}</title>
2344
+ <style>
2345
+ body{font-family:ui-sans-serif,system-ui,sans-serif;margin:0;background:#f8fafc;color:#0f172a}
2346
+ main{max-width:1100px;margin:0 auto;padding:32px}
2347
+ .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px}
2348
+ .card,.surface{background:white;border:1px solid #e2e8f0;border-radius:16px;padding:16px;box-shadow:0 12px 30px rgba(15,23,42,.06)}
2349
+ .status{display:inline-flex;border-radius:999px;padding:4px 10px;font-weight:700;background:#dcfce7;color:#166534}
2350
+ .status.fail{background:#fee2e2;color:#991b1b}.status.warn{background:#fef3c7;color:#92400e}
2351
+ .surfaces{display:grid;gap:14px;margin-top:18px}.muted{color:#64748b}
2352
+ code{background:#e2e8f0;border-radius:8px;padding:2px 6px}
2353
+ </style>
2354
+ </head>
2355
+ <body>
2356
+ <main>
2357
+ <p class="status ${report.status}">${escapeHtml7(report.status)}</p>
2358
+ <h1>${escapeHtml7(title)}</h1>
2359
+ <p class="muted">Runtime proof for why providers were selected, skipped, failed, or recovered by fallback.</p>
2360
+ <section class="grid">
2361
+ <article class="card"><strong>${String(report.summary.decisions)}</strong><p>decisions</p></article>
2362
+ <article class="card"><strong>${String(report.summary.providers)}</strong><p>providers</p></article>
2363
+ <article class="card"><strong>${String(report.summary.fallbacks)}</strong><p>fallbacks</p></article>
2364
+ <article class="card"><strong>${String(report.summary.degraded)}</strong><p>degraded</p></article>
2365
+ <article class="card"><strong>${String(report.summary.errors)}</strong><p>errors</p></article>
2366
+ </section>
2367
+ <section class="surfaces">
2368
+ ${report.surfaces.map((surface) => `<article class="surface">
2369
+ <header><strong>${escapeHtml7(surface.surface)}</strong> <span class="status ${surface.status}">${escapeHtml7(surface.status)}</span></header>
2370
+ <p>${String(surface.decisions)} decision(s), ${String(surface.fallbacks)} fallback(s), ${String(surface.degraded)} degraded decision(s), ${String(surface.errors)} error(s).</p>
2371
+ <p class="muted">Providers: ${escapeHtml7(surface.providers.join(", ") || "none")}</p>
2372
+ <p>${surface.reasons.map((reason) => `<code>${escapeHtml7(reason)}</code>`).join(" ")}</p>
2373
+ </article>`).join(`
2374
+ `)}
2375
+ </section>
2376
+ </main>
2377
+ </body>
2378
+ </html>`;
2379
+ var createVoiceProviderDecisionTraceRoutes = (options) => {
2380
+ const path = options.path ?? "/api/voice/provider-decisions";
2381
+ const htmlPath = options.htmlPath ?? "/voice/provider-decisions";
2382
+ const markdownPath = options.markdownPath ?? "/voice/provider-decisions.md";
2383
+ const headers = options.headers ?? {};
2384
+ const title = options.title ?? "Provider Decision Traces";
2385
+ const report = () => buildVoiceProviderDecisionTraceReport(options);
2386
+ const app = new Elysia3({ name: options.name ?? "voice-provider-decisions" }).get(path, async () => new Response(JSON.stringify(await report(), null, 2), {
2387
+ headers: {
2388
+ "content-type": "application/json; charset=utf-8",
2389
+ ...headers
2390
+ }
2391
+ }));
2392
+ if (htmlPath !== false) {
2393
+ app.get(htmlPath, async () => {
2394
+ const body = options.render ? await options.render(await report()) : renderVoiceProviderDecisionTraceHTML(await report(), title);
2395
+ return new Response(body, {
2396
+ headers: {
2397
+ "content-type": "text/html; charset=utf-8",
2398
+ ...headers
2399
+ }
2400
+ });
2401
+ });
2402
+ }
2403
+ if (markdownPath !== false) {
2404
+ app.get(markdownPath, async () => new Response(renderVoiceProviderDecisionTraceMarkdown(await report()), {
2405
+ headers: {
2406
+ "content-type": "text/markdown; charset=utf-8",
2407
+ ...headers
2408
+ }
2409
+ }));
2410
+ }
2411
+ return app;
2412
+ };
2413
+
2414
+ // src/trace.ts
2415
+ var createVoiceTraceEventId = (event) => [
2416
+ event.sessionId,
2417
+ event.turnId ?? "session",
2418
+ event.type,
2419
+ String(event.at ?? Date.now()),
2420
+ crypto.randomUUID()
2421
+ ].map(encodeURIComponent).join(":");
2422
+ var createVoiceTraceEvent = (event) => ({
2423
+ ...event,
2424
+ at: event.at,
2425
+ id: event.id ?? createVoiceTraceEventId({
2426
+ at: event.at,
2427
+ sessionId: event.sessionId,
2428
+ turnId: event.turnId,
2429
+ type: event.type
2430
+ })
2431
+ });
2432
+ var createVoiceTraceSinkDeliveryId = (events) => {
2433
+ const firstEvent = events[0];
2434
+ return [
2435
+ firstEvent?.sessionId ?? "trace",
2436
+ firstEvent?.traceId ?? "sink",
2437
+ String(firstEvent?.at ?? Date.now()),
2438
+ crypto.randomUUID()
2439
+ ].map(encodeURIComponent).join(":");
2440
+ };
2441
+ var createVoiceTraceSinkDeliveryRecord = (input) => {
2442
+ const createdAt = input.createdAt ?? Date.now();
2443
+ return {
2444
+ createdAt,
2445
+ deliveredAt: input.deliveredAt,
2446
+ deliveryAttempts: input.deliveryAttempts,
2447
+ deliveryError: input.deliveryError,
2448
+ deliveryStatus: input.deliveryStatus ?? "pending",
2449
+ events: input.events,
2450
+ id: input.id ?? createVoiceTraceSinkDeliveryId(input.events),
2451
+ sinkDeliveries: input.sinkDeliveries,
2452
+ updatedAt: input.updatedAt ?? createdAt
2453
+ };
2454
+ };
2455
+ var matchesTraceFilter = (event, filter) => {
2456
+ if (filter.sessionId !== undefined && event.sessionId !== filter.sessionId) {
2457
+ return false;
2458
+ }
2459
+ if (filter.turnId !== undefined && event.turnId !== filter.turnId) {
2460
+ return false;
2461
+ }
2462
+ if (filter.scenarioId !== undefined && event.scenarioId !== filter.scenarioId) {
2463
+ return false;
2464
+ }
2465
+ if (filter.traceId !== undefined && event.traceId !== filter.traceId) {
2466
+ return false;
2467
+ }
2468
+ if (filter.type !== undefined) {
2469
+ const types = Array.isArray(filter.type) ? filter.type : [filter.type];
2470
+ if (!types.includes(event.type)) {
2471
+ return false;
2472
+ }
2473
+ }
2474
+ return true;
2475
+ };
2476
+ var filterVoiceTraceEvents = (events, filter = {}) => {
2477
+ const sorted = events.filter((event) => matchesTraceFilter(event, filter)).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
2478
+ return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
2479
+ };
2480
+ var isPruneTimeMatch = (event, options) => {
2481
+ if (typeof options.before === "number" && event.at >= options.before) {
2482
+ return false;
2483
+ }
2484
+ if (typeof options.beforeOrAt === "number" && event.at > options.beforeOrAt) {
2485
+ return false;
2486
+ }
2487
+ return true;
2488
+ };
2489
+ var selectVoiceTraceEventsForPrune = (events, options = {}) => {
2490
+ let candidates = filterVoiceTraceEvents(events, options.filter).filter((event) => isPruneTimeMatch(event, options));
2491
+ if (typeof options.keepNewest === "number" && options.keepNewest >= 0) {
2492
+ const newestIds = new Set([...candidates].sort((left, right) => right.at - left.at || right.id.localeCompare(left.id)).slice(0, options.keepNewest).map((event) => event.id));
2493
+ candidates = candidates.filter((event) => !newestIds.has(event.id));
2494
+ }
2495
+ return typeof options.limit === "number" && options.limit >= 0 ? candidates.slice(0, options.limit) : candidates;
2496
+ };
2497
+ var pruneVoiceTraceEvents = async (options) => {
2498
+ const events = await options.store.list(options.filter);
2499
+ const deleted = selectVoiceTraceEventsForPrune(events, options);
2500
+ if (!options.dryRun) {
2501
+ await Promise.all(deleted.map((event) => options.store.remove(event.id)));
2502
+ }
2503
+ return {
2504
+ deleted,
2505
+ deletedCount: deleted.length,
2506
+ dryRun: Boolean(options.dryRun),
2507
+ scannedCount: events.length
2508
+ };
2509
+ };
2510
+ var sleep = async (delayMs) => {
2511
+ if (delayMs <= 0) {
2512
+ return;
2513
+ }
2514
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
2515
+ };
2516
+ var toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
2517
+ var signVoiceTraceSinkBody = async (input) => {
2518
+ const encoder = new TextEncoder;
2519
+ const key = await crypto.subtle.importKey("raw", encoder.encode(input.secret), {
2520
+ hash: "SHA-256",
2521
+ name: "HMAC"
2522
+ }, false, ["sign"]);
2523
+ const payload = encoder.encode(`${input.timestamp}.${input.body}`);
2524
+ const signature = await crypto.subtle.sign("HMAC", key, payload);
2525
+ return `sha256=${toHex(new Uint8Array(signature))}`;
2526
+ };
2527
+ var createVoiceTraceSinkDeliveryError = (input) => {
2528
+ if (input.response) {
2529
+ const statusText = input.response.statusText?.trim();
2530
+ return `Attempt ${input.attempt} failed with trace sink response ${input.response.status}${statusText ? ` ${statusText}` : ""}.`;
2531
+ }
2532
+ if (input.error instanceof Error) {
2533
+ return `Attempt ${input.attempt} failed: ${input.error.message}`;
2534
+ }
2535
+ return `Attempt ${input.attempt} failed: ${String(input.error)}`;
2536
+ };
2537
+ var normalizeVoiceTraceS3KeyPrefix = (prefix) => prefix?.trim().replace(/^\/+|\/+$/g, "") ?? "voice/trace-deliveries";
2538
+ var createVoiceTraceS3ObjectKey = (prefix, events) => {
2539
+ const firstEvent = events[0];
2540
+ const safeSessionId = encodeURIComponent(firstEvent?.sessionId ?? "trace");
2541
+ const safeEventId = encodeURIComponent(firstEvent?.id ?? crypto.randomUUID());
2542
+ return `${prefix}/${safeSessionId}/${Date.now()}-${safeEventId}.json`;
2543
+ };
2544
+ var resolveVoiceS3DeliveredTo = (options, key) => {
2545
+ const bucket = options.bucket;
2546
+ return bucket ? `s3://${bucket}/${key}` : `s3://${key}`;
2547
+ };
2548
+ var aggregateVoiceTraceSinkDeliveryStatus = (deliveries) => {
2549
+ const statuses = Object.values(deliveries).map((delivery) => delivery.status);
2550
+ if (statuses.length === 0 || statuses.every((status) => status === "skipped")) {
2551
+ return "skipped";
2552
+ }
2553
+ if (statuses.some((status) => status === "failed")) {
2554
+ return "failed";
2555
+ }
2556
+ return "delivered";
2557
+ };
2558
+ var createVoiceTraceHTTPSink = (options) => ({
2559
+ deliver: async ({ events }) => {
2560
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2561
+ if (typeof fetchImpl !== "function") {
2562
+ return {
2563
+ attempts: 0,
2564
+ deliveredTo: options.url,
2565
+ error: "Trace sink delivery failed: fetch is not available in this runtime.",
2566
+ eventCount: events.length,
2567
+ status: "failed"
2568
+ };
2569
+ }
2570
+ const maxRetries = Math.max(0, options.retries ?? 0);
2571
+ const backoffMs = Math.max(0, options.backoffMs ?? 250);
2572
+ const timeoutMs = Math.max(0, options.timeoutMs ?? 1e4);
2573
+ const payload = options.body ? await options.body({ events }) : {
2574
+ eventCount: events.length,
2575
+ events,
2576
+ source: "absolutejs-voice"
2577
+ };
2578
+ const body = JSON.stringify(payload);
2579
+ let lastError = "Trace sink delivery failed.";
2580
+ for (let attempt = 1;attempt <= maxRetries + 1; attempt += 1) {
2581
+ let controller;
2582
+ let timeout;
2583
+ try {
2584
+ const headers = {
2585
+ "content-type": "application/json",
2586
+ ...options.headers
2587
+ };
2588
+ if (options.signingSecret) {
2589
+ const timestamp = String(Date.now());
2590
+ headers["x-absolutejs-timestamp"] = timestamp;
2591
+ headers["x-absolutejs-signature"] = await signVoiceTraceSinkBody({
2592
+ body,
2593
+ secret: options.signingSecret,
2594
+ timestamp
2595
+ });
2596
+ }
2597
+ controller = timeoutMs > 0 ? new AbortController : undefined;
2598
+ if (controller && timeoutMs > 0) {
2599
+ timeout = setTimeout(() => controller?.abort(), timeoutMs);
2600
+ }
2601
+ const response = await fetchImpl(options.url, {
2602
+ body,
2603
+ headers,
2604
+ method: options.method ?? "POST",
2605
+ signal: controller?.signal
2606
+ });
2607
+ if (response.ok) {
2608
+ let responseBody;
2609
+ try {
2610
+ responseBody = await response.clone().json();
2611
+ } catch {
2612
+ responseBody = undefined;
2613
+ }
2614
+ return {
2615
+ attempts: attempt,
2616
+ deliveredAt: Date.now(),
2617
+ deliveredTo: options.url,
2618
+ eventCount: events.length,
2619
+ responseBody,
2620
+ status: "delivered"
2621
+ };
2622
+ }
2623
+ lastError = createVoiceTraceSinkDeliveryError({
2624
+ attempt,
2625
+ response
2626
+ });
2627
+ } catch (error) {
2628
+ lastError = createVoiceTraceSinkDeliveryError({
2629
+ attempt,
2630
+ error
2631
+ });
2632
+ } finally {
2633
+ if (timeout) {
2634
+ clearTimeout(timeout);
2635
+ }
2636
+ }
2637
+ if (attempt <= maxRetries) {
2638
+ await sleep(backoffMs * attempt);
2639
+ }
2640
+ }
2641
+ return {
2642
+ attempts: maxRetries + 1,
2643
+ deliveredTo: options.url,
2644
+ error: lastError,
2645
+ eventCount: events.length,
2646
+ status: "failed"
2647
+ };
2648
+ },
2649
+ eventTypes: options.eventTypes,
2650
+ id: options.id,
2651
+ kind: options.kind ?? "http"
2652
+ });
2653
+ var createVoiceTraceS3Sink = (options) => {
2654
+ const client = options.client ?? new Bun.S3Client(options);
2655
+ const keyPrefix = normalizeVoiceTraceS3KeyPrefix(options.keyPrefix);
2656
+ return {
2657
+ deliver: async ({ events }) => {
2658
+ const key = createVoiceTraceS3ObjectKey(keyPrefix, events);
2659
+ const payload = options.body ? await options.body({ events, key }) : {
2660
+ eventCount: events.length,
2661
+ events,
2662
+ key,
2663
+ source: "absolutejs-voice"
2664
+ };
2665
+ try {
2666
+ const file = client.file(key, options);
2667
+ await file.write(JSON.stringify(payload), {
2668
+ type: options.contentType ?? "application/json"
2669
+ });
2670
+ return {
2671
+ attempts: 1,
2672
+ deliveredAt: Date.now(),
2673
+ deliveredTo: resolveVoiceS3DeliveredTo(options, key),
2674
+ eventCount: events.length,
2675
+ responseBody: { key },
2676
+ status: "delivered"
2677
+ };
2678
+ } catch (error) {
2679
+ return {
2680
+ attempts: 1,
2681
+ deliveredTo: resolveVoiceS3DeliveredTo(options, key),
2682
+ error: error instanceof Error ? error.message : String(error),
2683
+ eventCount: events.length,
2684
+ status: "failed"
2685
+ };
2686
+ }
2687
+ },
2688
+ eventTypes: options.eventTypes,
2689
+ id: options.id,
2690
+ kind: options.kind ?? "s3"
2691
+ };
2692
+ };
2693
+ var deliverVoiceTraceEventsToSinks = async (input) => {
2694
+ const events = input.redact ? redactVoiceTraceEvents(input.events, input.redact) : input.events;
2695
+ const sinkDeliveries = {};
2696
+ for (const sink of input.sinks) {
2697
+ const sinkEvents = sink.eventTypes?.length ? events.filter((event) => sink.eventTypes?.includes(event.type)) : events;
2698
+ if (sinkEvents.length === 0) {
2699
+ sinkDeliveries[sink.id] = {
2700
+ attempts: 0,
2701
+ eventCount: 0,
2702
+ status: "skipped"
2703
+ };
2704
+ continue;
2705
+ }
2706
+ try {
2707
+ sinkDeliveries[sink.id] = await sink.deliver({
2708
+ events: sinkEvents
2709
+ });
2710
+ } catch (error) {
2711
+ sinkDeliveries[sink.id] = {
2712
+ attempts: 1,
2713
+ error: error instanceof Error ? error.message : String(error),
2714
+ eventCount: sinkEvents.length,
2715
+ status: "failed"
2716
+ };
2717
+ }
2718
+ }
2719
+ return {
2720
+ deliveredAt: Date.now(),
2721
+ eventCount: events.length,
2722
+ sinkDeliveries,
2723
+ status: aggregateVoiceTraceSinkDeliveryStatus(sinkDeliveries)
2724
+ };
2725
+ };
2726
+ var createVoiceTraceSinkStore = (options) => {
2727
+ const deliver = async (event) => {
2728
+ const result = await deliverVoiceTraceEventsToSinks({
2729
+ events: [event],
2730
+ redact: options.redact,
2731
+ sinks: options.sinks
2732
+ });
2733
+ await options.onDelivery?.(result);
2734
+ };
2735
+ return {
2736
+ append: async (event) => {
2737
+ const stored = await options.store.append(event);
2738
+ if (options.deliveryQueue) {
2739
+ const delivery2 = createVoiceTraceSinkDeliveryRecord({
2740
+ events: [stored]
2741
+ });
2742
+ await options.deliveryQueue.set(delivery2.id, delivery2);
2743
+ return stored;
2744
+ }
2745
+ const delivery = deliver(stored);
2746
+ if (options.awaitDelivery) {
2747
+ await delivery;
2748
+ } else {
2749
+ delivery.catch((error) => {
2750
+ options.onError?.(error);
2751
+ });
2752
+ }
2753
+ return stored;
2754
+ },
2755
+ get: (id) => options.store.get(id),
2756
+ list: (filter) => options.store.list(filter),
2757
+ remove: (id) => options.store.remove(id)
2758
+ };
2759
+ };
2760
+ var normalizeVoiceProfileTraceTaggerProfile = (profile) => typeof profile === "string" ? { id: profile } : profile?.id ? profile : undefined;
2761
+ var createVoiceProfileTraceTagger = (options) => {
2762
+ const profiles = new Map((options.profiles ?? []).map((profile) => [profile.id, profile]));
2763
+ const defaultProfile = normalizeVoiceProfileTraceTaggerProfile(options.defaultProfile);
2764
+ const resolveProfile = async (event) => {
2765
+ const resolved = normalizeVoiceProfileTraceTaggerProfile(await options.resolveProfile?.(event));
2766
+ const profile = resolved ?? defaultProfile;
2767
+ return profile ? profiles.get(profile.id) ?? profile : undefined;
2768
+ };
2769
+ return {
2770
+ append: async (event) => {
2771
+ const profile = await resolveProfile(event);
2772
+ if (!profile) {
2773
+ return options.store.append(event);
2774
+ }
2775
+ const metadata = {
2776
+ ...event.metadata ?? {},
2777
+ benchmarkProfileId: profile.id,
2778
+ profileDescription: event.metadata?.profileDescription ?? profile.description,
2779
+ profileId: profile.id,
2780
+ profileLabel: event.metadata?.profileLabel ?? profile.label
2781
+ };
2782
+ const payload = event.payload && typeof event.payload === "object" ? {
2783
+ ...event.payload,
2784
+ benchmarkProfileId: event.payload.benchmarkProfileId ?? profile.id,
2785
+ profileDescription: event.payload.profileDescription ?? profile.description,
2786
+ profileId: event.payload.profileId ?? profile.id,
2787
+ profileLabel: event.payload.profileLabel ?? profile.label
2788
+ } : event.payload;
2789
+ return options.store.append({
2790
+ ...event,
2791
+ metadata,
2792
+ payload
2793
+ });
2794
+ },
2795
+ get: (id) => options.store.get(id),
2796
+ list: (filter) => options.store.list(filter),
2797
+ remove: (id) => options.store.remove(id)
2798
+ };
2799
+ };
2800
+ var createVoiceMemoryTraceSinkDeliveryStore = () => {
2801
+ const deliveries = new Map;
2802
+ return {
2803
+ get: async (id) => deliveries.get(id),
2804
+ list: async () => [...deliveries.values()].sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id)),
2805
+ remove: async (id) => {
2806
+ deliveries.delete(id);
2807
+ },
2808
+ set: async (id, delivery) => {
2809
+ deliveries.set(id, delivery);
2810
+ }
2811
+ };
2812
+ };
2813
+ var createVoiceMemoryTraceEventStore = () => {
2814
+ const events = new Map;
2815
+ const append = async (event) => {
2816
+ const stored = createVoiceTraceEvent(event);
2817
+ events.set(stored.id, stored);
2818
+ return stored;
2819
+ };
2820
+ const get = async (id) => events.get(id);
2821
+ const list = async (filter) => filterVoiceTraceEvents([...events.values()], filter);
2822
+ const remove = async (id) => {
2823
+ events.delete(id);
2824
+ };
2825
+ return { append, get, list, remove };
2826
+ };
2827
+ var exportVoiceTrace = async (input) => {
2828
+ const events = await input.store.list(input.filter);
2829
+ return {
2830
+ exportedAt: Date.now(),
2831
+ events: input.redact ? redactVoiceTraceEvents(events, input.redact) : events,
2832
+ filter: input.filter,
2833
+ redacted: Boolean(input.redact)
2834
+ };
2835
+ };
2836
+ var toNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
2837
+ var escapeHtml8 = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2838
+ var formatTraceValue = (value) => {
2839
+ if (value === undefined || value === null) {
2840
+ return "";
2841
+ }
2842
+ if (typeof value === "string") {
2843
+ return value;
2844
+ }
2845
+ if (typeof value === "number" || typeof value === "boolean") {
2846
+ return String(value);
2847
+ }
2848
+ try {
2849
+ return JSON.stringify(value);
2850
+ } catch {
2851
+ return String(value);
2852
+ }
2853
+ };
2854
+ var DEFAULT_REDACTION_KEYS = [
2855
+ "apiKey",
2856
+ "authorization",
2857
+ "creditCard",
2858
+ "email",
2859
+ "externalId",
2860
+ "password",
2861
+ "phone",
2862
+ "secret",
2863
+ "ssn",
2864
+ "token"
2865
+ ];
2866
+ var DEFAULT_REDACTION_TEXT_KEYS = [
2867
+ "assistantText",
2868
+ "content",
2869
+ "error",
2870
+ "reason",
2871
+ "summary",
2872
+ "text"
2873
+ ];
2874
+ var EMAIL_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
2875
+ var PHONE_PATTERN = /(?<!\d)(?:\+?1[\s.-]?)?(?:\(?\d{3}\)?[\s.-]?)\d{3}[\s.-]?\d{4}(?!\d)/g;
2876
+ var normalizeRedactionKey = (key) => key.trim().toLowerCase().replace(/[^a-z0-9]/g, "");
2877
+ var resolveVoiceTraceRedactionOptions = (options = {}) => ({
2878
+ keys: typeof options === "boolean" ? DEFAULT_REDACTION_KEYS : options.keys ?? DEFAULT_REDACTION_KEYS,
2879
+ redactEmails: typeof options === "boolean" ? true : options.redactEmails ?? true,
2880
+ redactPhoneNumbers: typeof options === "boolean" ? true : options.redactPhoneNumbers ?? true,
2881
+ redactText: typeof options === "boolean" ? true : options.redactText ?? true,
2882
+ replacement: typeof options === "boolean" ? "[redacted]" : options.replacement ?? "[redacted]",
2883
+ textKeys: typeof options === "boolean" ? DEFAULT_REDACTION_TEXT_KEYS : options.textKeys ?? DEFAULT_REDACTION_TEXT_KEYS
2884
+ });
2885
+ var resolveReplacement = (input) => typeof input.options.replacement === "function" ? input.options.replacement({
2886
+ key: input.key,
2887
+ path: input.path,
2888
+ value: input.value
2889
+ }) : input.options.replacement;
2890
+ var redactVoiceTraceText = (value, options = {}, input = {}) => {
2891
+ const resolved = resolveVoiceTraceRedactionOptions(options);
2892
+ let redacted = value;
2893
+ const replacement = resolveReplacement({
2894
+ key: input.key,
2895
+ options: resolved,
2896
+ path: input.path ?? [],
2897
+ value
2898
+ });
2899
+ if (resolved.redactEmails) {
2900
+ redacted = redacted.replace(EMAIL_PATTERN, replacement);
2901
+ }
2902
+ if (resolved.redactPhoneNumbers) {
2903
+ redacted = redacted.replace(PHONE_PATTERN, replacement);
2904
+ }
2905
+ return redacted;
2906
+ };
2907
+ var redactTraceValue = (value, options, path) => {
2908
+ const key = path.at(-1);
2909
+ const normalizedKey = key ? normalizeRedactionKey(key) : undefined;
2910
+ const sensitiveKeys = new Set(options.keys.map(normalizeRedactionKey));
2911
+ const textKeys = new Set(options.textKeys.map(normalizeRedactionKey));
2912
+ if (normalizedKey && sensitiveKeys.has(normalizedKey) && (value === null || ["boolean", "number", "string", "undefined"].includes(typeof value))) {
2913
+ return resolveReplacement({
2914
+ key,
2915
+ options,
2916
+ path,
2917
+ value: String(value ?? "")
2918
+ });
2919
+ }
2920
+ if (typeof value === "string") {
2921
+ const shouldRedactText = options.redactText && (!normalizedKey || textKeys.has(normalizedKey) || path.length === 0);
2922
+ return shouldRedactText ? redactVoiceTraceText(value, options, {
2923
+ key,
2924
+ path
2925
+ }) : value;
2926
+ }
2927
+ if (Array.isArray(value)) {
2928
+ return value.map((item, index) => redactTraceValue(item, options, [...path, String(index)]));
2929
+ }
2930
+ if (typeof value === "object" && value) {
2931
+ return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [
2932
+ entryKey,
2933
+ redactTraceValue(entryValue, options, [...path, entryKey])
2934
+ ]));
2935
+ }
2936
+ return value;
2937
+ };
2938
+ var redactVoiceTraceEvent = (event, options = {}) => {
2939
+ const resolved = resolveVoiceTraceRedactionOptions(options);
2940
+ return {
2941
+ ...event,
2942
+ metadata: redactTraceValue(event.metadata, resolved, ["metadata"]),
2943
+ payload: redactTraceValue(event.payload, resolved, ["payload"])
2944
+ };
2945
+ };
2946
+ var redactVoiceTraceEvents = (events, options = {}) => events.map((event) => redactVoiceTraceEvent(event, options));
2947
+ var summarizeVoiceTrace = (events) => {
2948
+ const sorted = filterVoiceTraceEvents(events);
2949
+ const firstEvent = sorted[0];
2950
+ const lastEvent = sorted.at(-1);
2951
+ const lifecycleEvents = sorted.filter((event) => event.type === "call.lifecycle");
2952
+ const startEvent = lifecycleEvents.find((event) => event.payload.type === "start");
2953
+ const endEvent = lifecycleEvents.toReversed().find((event) => event.payload.type === "end");
2954
+ const costEvents = sorted.filter((event) => event.type === "turn.cost");
2955
+ const toolEvents = sorted.filter((event) => event.type === "agent.tool");
2956
+ const startedAt = startEvent?.at ?? firstEvent?.at;
2957
+ const endedAt = endEvent?.at ?? lastEvent?.at;
2958
+ const failed = sorted.some((event) => event.type === "session.error") || endEvent?.payload.disposition === "failed";
2959
+ return {
2960
+ assistantReplyCount: sorted.filter((event) => event.type === "turn.assistant").length,
2961
+ callDurationMs: startedAt !== undefined && endedAt !== undefined ? Math.max(0, endedAt - startedAt) : undefined,
2962
+ cost: {
2963
+ estimatedRelativeCostUnits: costEvents.reduce((total, event) => total + toNumber(event.payload.estimatedRelativeCostUnits), 0),
2964
+ totalBillableAudioMs: costEvents.reduce((total, event) => total + toNumber(event.payload.totalBillableAudioMs), 0)
2965
+ },
2966
+ endedAt,
2967
+ errorCount: sorted.filter((event) => event.type === "session.error").length,
2968
+ eventCount: sorted.length,
2969
+ failed,
2970
+ handoffCount: sorted.filter((event) => event.type === "agent.handoff").length,
2971
+ modelCallCount: sorted.filter((event) => event.type === "agent.model").length,
2972
+ sessionId: firstEvent?.sessionId,
2973
+ startedAt,
2974
+ toolCallCount: toolEvents.length,
2975
+ toolErrorCount: toolEvents.filter((event) => event.payload.status === "error").length,
2976
+ traceId: firstEvent?.traceId,
2977
+ transcriptCount: sorted.filter((event) => event.type === "turn.transcript").length,
2978
+ turnCount: sorted.filter((event) => event.type === "turn.committed").length
2979
+ };
2980
+ };
2981
+ var evaluateVoiceTrace = (events, options = {}) => {
2982
+ const summary = summarizeVoiceTrace(events);
2983
+ const issues = [];
2984
+ const maxHandoffs = options.maxHandoffs ?? 3;
2985
+ const maxToolErrors = options.maxToolErrors ?? 0;
2986
+ const maxModelCallsPerTurn = options.maxModelCallsPerTurn ?? 6;
2987
+ const turnCountForRatio = Math.max(1, summary.turnCount);
2988
+ if (options.requireCompletedCall !== false && !summary.endedAt) {
2989
+ issues.push({
2990
+ code: "call-not-ended",
2991
+ message: "Trace does not include a call end lifecycle event.",
2992
+ severity: "warning"
2993
+ });
2994
+ }
2995
+ if (summary.failed) {
2996
+ issues.push({
2997
+ code: "session-error",
2998
+ message: "Trace contains a session error or failed call disposition.",
2999
+ severity: "error"
3000
+ });
3001
+ }
3002
+ if (options.requireTranscript !== false && summary.transcriptCount === 0) {
3003
+ issues.push({
3004
+ code: "missing-transcript",
3005
+ message: "Trace does not include any transcript events.",
3006
+ severity: "error"
3007
+ });
3008
+ }
3009
+ if (options.requireTurn !== false && summary.turnCount === 0) {
3010
+ issues.push({
3011
+ code: "missing-turn",
3012
+ message: "Trace does not include any committed turns.",
3013
+ severity: "error"
3014
+ });
3015
+ }
3016
+ if (options.requireAssistantReply !== false && summary.turnCount > 0 && summary.assistantReplyCount === 0) {
3017
+ issues.push({
3018
+ code: "missing-assistant-reply",
3019
+ message: "Trace has committed turns but no assistant replies.",
3020
+ severity: "warning"
3021
+ });
3022
+ }
3023
+ if (summary.toolErrorCount > maxToolErrors) {
3024
+ issues.push({
3025
+ code: "tool-errors",
3026
+ message: `Trace has ${summary.toolErrorCount} tool error(s), above the allowed ${maxToolErrors}.`,
3027
+ severity: "error"
3028
+ });
3029
+ }
3030
+ if (summary.handoffCount > maxHandoffs) {
3031
+ issues.push({
3032
+ code: "too-many-handoffs",
3033
+ message: `Trace has ${summary.handoffCount} handoff(s), above the allowed ${maxHandoffs}.`,
3034
+ severity: "warning"
3035
+ });
3036
+ }
3037
+ if (summary.modelCallCount / turnCountForRatio > maxModelCallsPerTurn) {
3038
+ issues.push({
3039
+ code: "too-many-model-calls",
3040
+ message: `Trace averages more than ${maxModelCallsPerTurn} model calls per committed turn.`,
3041
+ severity: "warning"
3042
+ });
3043
+ }
3044
+ return {
3045
+ issues,
3046
+ pass: !issues.some((issue) => issue.severity === "error"),
3047
+ summary
3048
+ };
3049
+ };
3050
+ var renderTraceEventMarkdown = (event, startedAt) => {
3051
+ const offset = startedAt === undefined ? `${event.at}` : `+${Math.max(0, event.at - startedAt)}ms`;
3052
+ const label = `- ${offset} [${event.type}]`;
3053
+ switch (event.type) {
3054
+ case "turn.transcript":
3055
+ return `${label} ${event.payload.isFinal ? "final" : "partial"} "${formatTraceValue(event.payload.text)}"`;
3056
+ case "turn.committed":
3057
+ return `${label} committed "${formatTraceValue(event.payload.text)}"`;
3058
+ case "turn.assistant":
3059
+ return event.payload.text ? `${label} assistant "${formatTraceValue(event.payload.text)}"` : `${label} ${formatTraceValue(event.payload.status)}`;
3060
+ case "agent.tool":
3061
+ return `${label} ${formatTraceValue(event.payload.toolName)} ${formatTraceValue(event.payload.status)}`;
3062
+ case "agent.context":
3063
+ return `${label} ${formatTraceValue(event.payload.fromAgentId)} -> ${formatTraceValue(event.payload.targetAgentId)} ${formatTraceValue(event.payload.status)}`;
3064
+ case "agent.handoff":
3065
+ return `${label} ${formatTraceValue(event.payload.fromAgentId)} -> ${formatTraceValue(event.payload.targetAgentId)}`;
3066
+ case "session.error":
3067
+ return `${label} ${formatTraceValue(event.payload.error)}`;
3068
+ case "call.lifecycle":
3069
+ return `${label} ${formatTraceValue(event.payload.type)} ${formatTraceValue(event.payload.disposition)}`.trim();
3070
+ default:
3071
+ return `${label} ${formatTraceValue(event.payload)}`;
3072
+ }
3073
+ };
3074
+ var renderVoiceTraceMarkdown = (events, options = {}) => {
3075
+ const sorted = filterVoiceTraceEvents(options.redact ? redactVoiceTraceEvents(events, options.redact) : events);
3076
+ const summary = summarizeVoiceTrace(sorted);
3077
+ const evaluation = evaluateVoiceTrace(sorted, options.evaluation);
3078
+ const lines = [
3079
+ `# ${options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim()}`,
3080
+ "",
3081
+ `Pass: ${evaluation.pass ? "yes" : "no"}`,
3082
+ `Session: ${summary.sessionId ?? "unknown"}`,
3083
+ `Events: ${summary.eventCount}`,
3084
+ `Turns: ${summary.turnCount}`,
3085
+ `Transcripts: ${summary.transcriptCount}`,
3086
+ `Assistant replies: ${summary.assistantReplyCount}`,
3087
+ `Model calls: ${summary.modelCallCount}`,
3088
+ `Tool calls: ${summary.toolCallCount}`,
3089
+ `Handoffs: ${summary.handoffCount}`,
3090
+ `Errors: ${summary.errorCount}`,
3091
+ `Estimated cost units: ${summary.cost.estimatedRelativeCostUnits}`,
3092
+ ""
3093
+ ];
3094
+ if (evaluation.issues.length > 0) {
3095
+ lines.push("## Issues", "");
3096
+ for (const issue of evaluation.issues) {
3097
+ lines.push(`- [${issue.severity}] ${issue.code}: ${issue.message}`);
3098
+ }
3099
+ lines.push("");
3100
+ }
3101
+ lines.push("## Timeline", "");
3102
+ for (const event of sorted) {
3103
+ lines.push(renderTraceEventMarkdown(event, summary.startedAt));
3104
+ }
3105
+ return lines.join(`
3106
+ `);
3107
+ };
3108
+ var renderVoiceTraceHTML = (events, options = {}) => {
3109
+ const markdown = renderVoiceTraceMarkdown(events, options);
3110
+ const renderEvents = options.redact ? redactVoiceTraceEvents(events, options.redact) : events;
3111
+ const summary = summarizeVoiceTrace(renderEvents);
3112
+ const evaluation = evaluateVoiceTrace(renderEvents, options.evaluation);
3113
+ const eventRows = filterVoiceTraceEvents(renderEvents).map((event) => {
3114
+ const offset = summary.startedAt === undefined ? event.at : Math.max(0, event.at - summary.startedAt);
3115
+ return [
3116
+ "<tr>",
3117
+ `<td>${escapeHtml8(String(offset))}</td>`,
3118
+ `<td>${escapeHtml8(event.type)}</td>`,
3119
+ `<td>${escapeHtml8(event.turnId ?? "")}</td>`,
3120
+ `<td><code>${escapeHtml8(JSON.stringify(event.payload))}</code></td>`,
3121
+ "</tr>"
3122
+ ].join("");
3123
+ }).join(`
3124
+ `);
3125
+ return [
3126
+ "<!doctype html>",
3127
+ '<html lang="en">',
3128
+ "<head>",
3129
+ '<meta charset="utf-8" />',
3130
+ '<meta name="viewport" content="width=device-width, initial-scale=1" />',
3131
+ `<title>${escapeHtml8(options.title ?? "Voice Trace")}</title>`,
3132
+ "<style>",
3133
+ "body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;line-height:1.45;background:#f8f7f2;color:#181713}",
3134
+ "main{max-width:1100px;margin:auto}",
3135
+ ".summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:.75rem;margin:1rem 0}",
3136
+ ".card{background:white;border:1px solid #ded9cc;border-radius:12px;padding:1rem}",
3137
+ ".pass{color:#126b3a}.fail{color:#9d2222}",
3138
+ "table{border-collapse:collapse;width:100%;background:white;border:1px solid #ded9cc}",
3139
+ "th,td{border-bottom:1px solid #eee8dc;padding:.65rem;text-align:left;vertical-align:top}",
3140
+ "code{white-space:pre-wrap;word-break:break-word}",
3141
+ "pre{background:#181713;color:#f8f7f2;padding:1rem;border-radius:12px;overflow:auto}",
3142
+ "</style>",
3143
+ "</head>",
3144
+ "<body><main>",
3145
+ `<h1>${escapeHtml8(options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim())}</h1>`,
3146
+ `<p class="${evaluation.pass ? "pass" : "fail"}">QA: ${evaluation.pass ? "pass" : "fail"}</p>`,
3147
+ '<section class="summary">',
3148
+ `<div class="card"><strong>Events</strong><br>${summary.eventCount}</div>`,
3149
+ `<div class="card"><strong>Turns</strong><br>${summary.turnCount}</div>`,
3150
+ `<div class="card"><strong>Transcripts</strong><br>${summary.transcriptCount}</div>`,
3151
+ `<div class="card"><strong>Tool errors</strong><br>${summary.toolErrorCount}</div>`,
3152
+ `<div class="card"><strong>Cost units</strong><br>${summary.cost.estimatedRelativeCostUnits}</div>`,
3153
+ "</section>",
3154
+ "<h2>Timeline</h2>",
3155
+ "<table><thead><tr><th>Offset ms</th><th>Type</th><th>Turn</th><th>Payload</th></tr></thead><tbody>",
3156
+ eventRows,
3157
+ "</tbody></table>",
3158
+ "<h2>Markdown Export</h2>",
3159
+ `<pre>${escapeHtml8(markdown)}</pre>`,
3160
+ "</main></body></html>"
3161
+ ].join(`
3162
+ `);
3163
+ };
3164
+ var buildVoiceTraceReplay = (events, options = {}) => ({
3165
+ evaluation: evaluateVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events, options.evaluation),
3166
+ html: renderVoiceTraceHTML(events, options),
3167
+ markdown: renderVoiceTraceMarkdown(events, options),
3168
+ summary: summarizeVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events)
3169
+ });
3170
+
3171
+ // src/proofTrends.ts
1419
3172
  var DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS = 24 * 60 * 60 * 1000;
1420
3173
  var DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS = [
1421
3174
  {
@@ -1748,6 +3501,9 @@ var buildVoiceRealCallProfileEvidenceFromTraceEvents = (events, options = {}) =>
1748
3501
  const providerP95Ms = maxNumber(providers.map((provider) => provider.p95Ms));
1749
3502
  const runtimeChannel = summarizeRuntimeChannelTraceEvidence(sessionEvents);
1750
3503
  const surfaces = readRealCallProfileTraceSurfaces(sessionEvents);
3504
+ if (providers.length === 0 && runtimeChannel === undefined && liveLatencies.length === 0 && surfaces.length === 0) {
3505
+ return;
3506
+ }
1751
3507
  return {
1752
3508
  generatedAt: new Date(Math.max(...sessionEvents.map((event) => event.at))).toISOString(),
1753
3509
  liveP95Ms: percentile(liveLatencies, 95),
@@ -2212,6 +3968,98 @@ var uniqueVoiceRealCallProfileRecoveryLoopActions = (actions) => {
2212
3968
  return true;
2213
3969
  });
2214
3970
  };
3971
+ var normalizeRealCallProfileRecoveryProvider = (role, provider) => {
3972
+ if (!provider) {
3973
+ return;
3974
+ }
3975
+ if (typeof provider === "string") {
3976
+ return {
3977
+ provider,
3978
+ selectedProvider: provider,
3979
+ status: "selected"
3980
+ };
3981
+ }
3982
+ return {
3983
+ ...provider,
3984
+ selectedProvider: provider.selectedProvider ?? provider.provider,
3985
+ status: provider.status ?? "selected"
3986
+ };
3987
+ };
3988
+ var profileRealCallRecoveryEvent = (event, profileId, metadata = {}) => ({
3989
+ ...event,
3990
+ metadata: {
3991
+ ...metadata,
3992
+ ...event.metadata ?? {},
3993
+ benchmarkProfileId: event.metadata?.benchmarkProfileId ?? profileId,
3994
+ profileId: event.metadata?.profileId ?? profileId
3995
+ },
3996
+ payload: {
3997
+ ...event.payload ?? {},
3998
+ benchmarkProfileId: event.payload.benchmarkProfileId ?? profileId,
3999
+ profileId: event.payload.profileId ?? profileId
4000
+ }
4001
+ });
4002
+ var appendVoiceRealCallProfileRecoveryEvidence = async (options) => {
4003
+ const at = options.at ?? Date.now();
4004
+ const scenarioId = options.scenarioId ?? "real-call-profile-recovery";
4005
+ const sessionId = options.sessionId ?? `real-call-profile-recovery-${options.profileId}-${new Date(at).toISOString()}`;
4006
+ const browser = options.browser ?? {};
4007
+ const live = options.live ?? {};
4008
+ const providerEvents = [
4009
+ ["llm", 320, "live-call"],
4010
+ ["stt", 82, "live-stt"],
4011
+ ["tts", 45, "live-tts"]
4012
+ ].flatMap(([role, defaultElapsedMs, defaultSurface], index) => {
4013
+ const provider = normalizeRealCallProfileRecoveryProvider(role, options.providers?.[role]);
4014
+ if (!provider) {
4015
+ return [];
4016
+ }
4017
+ return [
4018
+ profileRealCallRecoveryEvent(createVoiceProviderDecisionTraceEvent({
4019
+ at: at + index,
4020
+ elapsedMs: provider.elapsedMs ?? defaultElapsedMs,
4021
+ kind: role,
4022
+ provider: provider.provider,
4023
+ reason: provider.reason ?? `Real-call profile recovery selected ${provider.provider} for ${role.toUpperCase()} evidence.`,
4024
+ scenarioId,
4025
+ selectedProvider: provider.selectedProvider,
4026
+ sessionId,
4027
+ status: provider.status,
4028
+ surface: provider.surface ?? defaultSurface
4029
+ }), options.profileId, options.metadata)
4030
+ ];
4031
+ });
4032
+ const browserEvents = browser === false ? [] : [
4033
+ profileRealCallRecoveryEvent(createVoiceTraceEvent({
4034
+ at: at + 3,
4035
+ payload: {
4036
+ firstAudioLatencyMs: browser.firstAudioLatencyMs ?? 420,
4037
+ messageCount: browser.messageCount,
4038
+ openSockets: browser.openSockets,
4039
+ receivedBytes: browser.receivedBytes,
4040
+ sentBytes: browser.sentBytes,
4041
+ status: browser.status ?? "pass"
4042
+ },
4043
+ scenarioId,
4044
+ sessionId,
4045
+ type: "client.browser_media"
4046
+ }), options.profileId, options.metadata)
4047
+ ];
4048
+ const liveEvents = live === false ? [] : [
4049
+ profileRealCallRecoveryEvent(createVoiceTraceEvent({
4050
+ at: at + 4,
4051
+ payload: {
4052
+ latencyMs: live.latencyMs ?? 420,
4053
+ status: live.status ?? "pass"
4054
+ },
4055
+ scenarioId,
4056
+ sessionId,
4057
+ type: "client.live_latency"
4058
+ }), options.profileId, options.metadata)
4059
+ ];
4060
+ const events = await Promise.all([...providerEvents, ...browserEvents, ...liveEvents].map((event) => options.store.append(event)));
4061
+ return { events, sessionId };
4062
+ };
2215
4063
  var runVoiceRealCallProfileRecoveryLoop = async (options) => {
2216
4064
  const baseUrl = options.baseUrl.replace(/\/$/, "");
2217
4065
  const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
@@ -3040,7 +4888,7 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
3040
4888
  }
3041
4889
  };
3042
4890
  };
3043
- var escapeHtml5 = (value) => String(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4891
+ var escapeHtml9 = (value) => String(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3044
4892
  var escapeMarkdown = (value) => value.replaceAll("|", "\\|");
3045
4893
  var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provider Runtime Recommendations") => [
3046
4894
  `# ${title}`,
@@ -3073,11 +4921,11 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
3073
4921
  ].join(`
3074
4922
  `);
3075
4923
  var renderVoiceProofTrendRecommendationHTML = (report, title = "Voice Provider Runtime Recommendations") => {
3076
- const cards = report.recommendations.map((recommendation) => `<article class="${escapeHtml5(recommendation.status)}"><p class="eyebrow">${escapeHtml5(recommendation.surface)} \xB7 ${escapeHtml5(recommendation.status)}</p><h2>${escapeHtml5(recommendation.recommendation)}</h2><p>${escapeHtml5(recommendation.nextMove)}</p><pre>${escapeHtml5(JSON.stringify(recommendation.evidence, null, 2))}</pre></article>`).join("");
3077
- const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml5(issue)}</li>`).join("");
3078
- const providerRows = report.providers.length === 0 ? "<li>No provider-specific samples were present.</li>" : report.providers.map((provider) => `<li><strong>#${String(provider.rank)} ${escapeHtml5(provider.label ?? provider.id)}</strong><span>${escapeHtml5(provider.role ?? "provider")} \xB7 ${escapeHtml5(provider.status)} \xB7 p95 ${escapeHtml5(provider.p95Ms ?? "n/a")}ms \xB7 ${escapeHtml5(provider.samples ?? "n/a")} sample(s)</span><small>${escapeHtml5(provider.nextMove)}</small></li>`).join("");
3079
- const profileRows = report.profiles.length === 0 ? "<li>No benchmark profiles were present.</li>" : report.profiles.map((profile) => `<li><strong>${escapeHtml5(profile.label ?? profile.id)}</strong><span>${escapeHtml5(profile.status)} \xB7 ${escapeHtml5(formatProviderMix(profile.bestProviders))}</span><small>${escapeHtml5(profile.nextMove)}</small></li>`).join("");
3080
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>${escapeHtml5(title)}</title><style>body{background:#101418;color:#f7f3e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero,article{background:#17201d;border:1px solid #2e3d36;border-radius:24px;margin-bottom:16px;padding:22px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.18),rgba(245,158,11,.12))}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.1em;text-transform:uppercase}h1{font-size:clamp(2.2rem,6vw,4.7rem);letter-spacing:-.06em;line-height:.92;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{border:1px solid #42534a;border-radius:999px;padding:8px 12px}.pass{border-color:rgba(34,197,94,.55)}.warn{border-color:rgba(245,158,11,.7)}.fail{border-color:rgba(239,68,68,.75)}pre{background:#0b1110;border-radius:14px;overflow:auto;padding:12px}a{color:#5eead4}li{margin:.45rem 0}li span,li small{display:block;color:#c9d3ca}</style></head><body><main><section class="hero"><p class="eyebrow">Sustained proof recommendations</p><h1>${escapeHtml5(title)}</h1><p>Generated ${escapeHtml5(report.generatedAt)} from ${escapeHtml5(report.source)}.</p><div class="summary"><span class="pill">Status ${escapeHtml5(report.status)}</span><span class="pill">Provider ${report.summary.keepCurrentProviderPath ? "keep" : "change"}</span><span class="pill">Best mix ${escapeHtml5(formatProviderMix(report.bestProviders))}</span><span class="pill">Profiles ${String(report.profiles.length)}</span><span class="pill">Runtime ${report.summary.keepCurrentRuntimeChannel ? "keep" : "tune"}</span><span class="pill">${String(report.summary.recommendedActions)} action(s)</span></div></section>${cards}<section class="hero"><h2>Benchmark Profiles</h2><ul>${profileRows}</ul></section><section class="hero"><h2>Provider Comparison</h2><ul>${providerRows}</ul></section><section class="hero"><h2>Issues</h2><ul>${issues}</ul></section></main></body></html>`;
4924
+ const cards = report.recommendations.map((recommendation) => `<article class="${escapeHtml9(recommendation.status)}"><p class="eyebrow">${escapeHtml9(recommendation.surface)} \xB7 ${escapeHtml9(recommendation.status)}</p><h2>${escapeHtml9(recommendation.recommendation)}</h2><p>${escapeHtml9(recommendation.nextMove)}</p><pre>${escapeHtml9(JSON.stringify(recommendation.evidence, null, 2))}</pre></article>`).join("");
4925
+ const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml9(issue)}</li>`).join("");
4926
+ const providerRows = report.providers.length === 0 ? "<li>No provider-specific samples were present.</li>" : report.providers.map((provider) => `<li><strong>#${String(provider.rank)} ${escapeHtml9(provider.label ?? provider.id)}</strong><span>${escapeHtml9(provider.role ?? "provider")} \xB7 ${escapeHtml9(provider.status)} \xB7 p95 ${escapeHtml9(provider.p95Ms ?? "n/a")}ms \xB7 ${escapeHtml9(provider.samples ?? "n/a")} sample(s)</span><small>${escapeHtml9(provider.nextMove)}</small></li>`).join("");
4927
+ const profileRows = report.profiles.length === 0 ? "<li>No benchmark profiles were present.</li>" : report.profiles.map((profile) => `<li><strong>${escapeHtml9(profile.label ?? profile.id)}</strong><span>${escapeHtml9(profile.status)} \xB7 ${escapeHtml9(formatProviderMix(profile.bestProviders))}</span><small>${escapeHtml9(profile.nextMove)}</small></li>`).join("");
4928
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>${escapeHtml9(title)}</title><style>body{background:#101418;color:#f7f3e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero,article{background:#17201d;border:1px solid #2e3d36;border-radius:24px;margin-bottom:16px;padding:22px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.18),rgba(245,158,11,.12))}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.1em;text-transform:uppercase}h1{font-size:clamp(2.2rem,6vw,4.7rem);letter-spacing:-.06em;line-height:.92;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{border:1px solid #42534a;border-radius:999px;padding:8px 12px}.pass{border-color:rgba(34,197,94,.55)}.warn{border-color:rgba(245,158,11,.7)}.fail{border-color:rgba(239,68,68,.75)}pre{background:#0b1110;border-radius:14px;overflow:auto;padding:12px}a{color:#5eead4}li{margin:.45rem 0}li span,li small{display:block;color:#c9d3ca}</style></head><body><main><section class="hero"><p class="eyebrow">Sustained proof recommendations</p><h1>${escapeHtml9(title)}</h1><p>Generated ${escapeHtml9(report.generatedAt)} from ${escapeHtml9(report.source)}.</p><div class="summary"><span class="pill">Status ${escapeHtml9(report.status)}</span><span class="pill">Provider ${report.summary.keepCurrentProviderPath ? "keep" : "change"}</span><span class="pill">Best mix ${escapeHtml9(formatProviderMix(report.bestProviders))}</span><span class="pill">Profiles ${String(report.profiles.length)}</span><span class="pill">Runtime ${report.summary.keepCurrentRuntimeChannel ? "keep" : "tune"}</span><span class="pill">${String(report.summary.recommendedActions)} action(s)</span></div></section>${cards}<section class="hero"><h2>Benchmark Profiles</h2><ul>${profileRows}</ul></section><section class="hero"><h2>Provider Comparison</h2><ul>${providerRows}</ul></section><section class="hero"><h2>Issues</h2><ul>${issues}</ul></section></main></body></html>`;
3081
4929
  };
3082
4930
  var renderVoiceRealCallProfileHistoryMarkdown = (report, title = "Voice Real-Call Profile History") => [
3083
4931
  `# ${title}`,
@@ -3111,18 +4959,18 @@ var renderVoiceRealCallProfileHistoryMarkdown = (report, title = "Voice Real-Cal
3111
4959
  ].join(`
3112
4960
  `);
3113
4961
  var renderVoiceRealCallProfileHistoryHTML = (report, title = "Voice Real-Call Profile History") => {
3114
- const profileRows = report.summary.profiles?.length ? report.summary.profiles.map((profile) => `<tr><td>${escapeHtml5(profile.label ?? profile.id)}</td><td>${escapeHtml5(profile.status ?? "unknown")}</td><td>${escapeHtml5(profile.maxLiveP95Ms ?? "n/a")}</td><td>${escapeHtml5(profile.maxProviderP95Ms ?? "n/a")}</td><td>${escapeHtml5(profile.maxTurnP95Ms ?? "n/a")}</td><td>${escapeHtml5(formatProviderMix(profile.providers ?? []))}</td></tr>`).join("") : '<tr><td colspan="6">No profiles present.</td></tr>';
3115
- const defaultRows = report.defaults.profiles.length > 0 ? report.defaults.profiles.map((profile) => `<tr><td>${escapeHtml5(profile.label ?? profile.profileId)}</td><td>${escapeHtml5(profile.status)}</td><td>${escapeHtml5(Object.entries(profile.providerRoutes).map(([role, provider]) => `${role}: ${provider}`).join(", ") || "n/a")}</td><td>${escapeHtml5(profile.latencyBudgets.maxLiveP95Ms ?? "n/a")}</td><td>${escapeHtml5(profile.latencyBudgets.maxProviderP95Ms ?? "n/a")}</td><td>${escapeHtml5(profile.latencyBudgets.maxTurnP95Ms ?? "n/a")}</td></tr>`).join("") : '<tr><td colspan="6">No actionable defaults present.</td></tr>';
3116
- const recommendations = report.recommendations.recommendations.map((recommendation) => `<article class="${escapeHtml5(recommendation.status)}"><p class="eyebrow">${escapeHtml5(recommendation.surface)} \xB7 ${escapeHtml5(recommendation.status)}</p><h2>${escapeHtml5(recommendation.recommendation)}</h2><p>${escapeHtml5(recommendation.nextMove)}</p></article>`).join("");
3117
- const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml5(issue)}</li>`).join("");
3118
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>${escapeHtml5(title)}</title><style>body{background:#111510;color:#f6f0dd;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero,article,.card{background:#182117;border:1px solid #32412d;border-radius:24px;margin-bottom:16px;padding:22px}.hero{background:linear-gradient(135deg,rgba(132,204,22,.16),rgba(20,184,166,.12))}.eyebrow{color:#bef264;font-weight:900;letter-spacing:.1em;text-transform:uppercase}h1{font-size:clamp(2.2rem,6vw,4.7rem);letter-spacing:-.06em;line-height:.92;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{border:1px solid #52624b;border-radius:999px;padding:8px 12px}.pass{border-color:rgba(34,197,94,.55)}.warn{border-color:rgba(245,158,11,.7)}.fail{border-color:rgba(239,68,68,.75)}table{border-collapse:collapse;width:100%}td,th{border-bottom:1px solid #32412d;padding:10px;text-align:left}</style></head><body><main><section class="hero"><p class="eyebrow">Real-call benchmark history</p><h1>${escapeHtml5(title)}</h1><p>Generated ${escapeHtml5(report.generatedAt)} from ${escapeHtml5(report.source)}.</p><div class="summary"><span class="pill">Status ${escapeHtml5(report.status)}</span><span class="pill">Reports ${String(report.reports)}</span><span class="pill">Profiles ${String(report.summary.profileCount)}</span><span class="pill">Defaults ${String(report.defaults.summary.actionableProfiles)}/${String(report.defaults.summary.profileCount)}</span><span class="pill">Cycles ${String(report.summary.cycles ?? 0)}</span><span class="pill">Best mix ${escapeHtml5(formatProviderMix(report.recommendations.bestProviders))}</span></div></section><section class="card"><h2>Profiles</h2><table><thead><tr><th>Profile</th><th>Status</th><th>Live p95</th><th>Provider p95</th><th>Turn p95</th><th>Provider mix</th></tr></thead><tbody>${profileRows}</tbody></table></section><section class="card"><h2>Actionable Defaults</h2><table><thead><tr><th>Profile</th><th>Status</th><th>Provider routes</th><th>Live budget</th><th>Provider budget</th><th>Turn budget</th></tr></thead><tbody>${defaultRows}</tbody></table></section>${recommendations}<section class="card"><h2>Issues</h2><ul>${issues}</ul></section></main></body></html>`;
4962
+ const profileRows = report.summary.profiles?.length ? report.summary.profiles.map((profile) => `<tr><td>${escapeHtml9(profile.label ?? profile.id)}</td><td>${escapeHtml9(profile.status ?? "unknown")}</td><td>${escapeHtml9(profile.maxLiveP95Ms ?? "n/a")}</td><td>${escapeHtml9(profile.maxProviderP95Ms ?? "n/a")}</td><td>${escapeHtml9(profile.maxTurnP95Ms ?? "n/a")}</td><td>${escapeHtml9(formatProviderMix(profile.providers ?? []))}</td></tr>`).join("") : '<tr><td colspan="6">No profiles present.</td></tr>';
4963
+ const defaultRows = report.defaults.profiles.length > 0 ? report.defaults.profiles.map((profile) => `<tr><td>${escapeHtml9(profile.label ?? profile.profileId)}</td><td>${escapeHtml9(profile.status)}</td><td>${escapeHtml9(Object.entries(profile.providerRoutes).map(([role, provider]) => `${role}: ${provider}`).join(", ") || "n/a")}</td><td>${escapeHtml9(profile.latencyBudgets.maxLiveP95Ms ?? "n/a")}</td><td>${escapeHtml9(profile.latencyBudgets.maxProviderP95Ms ?? "n/a")}</td><td>${escapeHtml9(profile.latencyBudgets.maxTurnP95Ms ?? "n/a")}</td></tr>`).join("") : '<tr><td colspan="6">No actionable defaults present.</td></tr>';
4964
+ const recommendations = report.recommendations.recommendations.map((recommendation) => `<article class="${escapeHtml9(recommendation.status)}"><p class="eyebrow">${escapeHtml9(recommendation.surface)} \xB7 ${escapeHtml9(recommendation.status)}</p><h2>${escapeHtml9(recommendation.recommendation)}</h2><p>${escapeHtml9(recommendation.nextMove)}</p></article>`).join("");
4965
+ const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml9(issue)}</li>`).join("");
4966
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>${escapeHtml9(title)}</title><style>body{background:#111510;color:#f6f0dd;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero,article,.card{background:#182117;border:1px solid #32412d;border-radius:24px;margin-bottom:16px;padding:22px}.hero{background:linear-gradient(135deg,rgba(132,204,22,.16),rgba(20,184,166,.12))}.eyebrow{color:#bef264;font-weight:900;letter-spacing:.1em;text-transform:uppercase}h1{font-size:clamp(2.2rem,6vw,4.7rem);letter-spacing:-.06em;line-height:.92;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{border:1px solid #52624b;border-radius:999px;padding:8px 12px}.pass{border-color:rgba(34,197,94,.55)}.warn{border-color:rgba(245,158,11,.7)}.fail{border-color:rgba(239,68,68,.75)}table{border-collapse:collapse;width:100%}td,th{border-bottom:1px solid #32412d;padding:10px;text-align:left}</style></head><body><main><section class="hero"><p class="eyebrow">Real-call benchmark history</p><h1>${escapeHtml9(title)}</h1><p>Generated ${escapeHtml9(report.generatedAt)} from ${escapeHtml9(report.source)}.</p><div class="summary"><span class="pill">Status ${escapeHtml9(report.status)}</span><span class="pill">Reports ${String(report.reports)}</span><span class="pill">Profiles ${String(report.summary.profileCount)}</span><span class="pill">Defaults ${String(report.defaults.summary.actionableProfiles)}/${String(report.defaults.summary.profileCount)}</span><span class="pill">Cycles ${String(report.summary.cycles ?? 0)}</span><span class="pill">Best mix ${escapeHtml9(formatProviderMix(report.recommendations.bestProviders))}</span></div></section><section class="card"><h2>Profiles</h2><table><thead><tr><th>Profile</th><th>Status</th><th>Live p95</th><th>Provider p95</th><th>Turn p95</th><th>Provider mix</th></tr></thead><tbody>${profileRows}</tbody></table></section><section class="card"><h2>Actionable Defaults</h2><table><thead><tr><th>Profile</th><th>Status</th><th>Provider routes</th><th>Live budget</th><th>Provider budget</th><th>Turn budget</th></tr></thead><tbody>${defaultRows}</tbody></table></section>${recommendations}<section class="card"><h2>Issues</h2><ul>${issues}</ul></section></main></body></html>`;
3119
4967
  };
3120
4968
  var createVoiceProofTrendRecommendationRoutes = (options) => {
3121
4969
  const path = options.path ?? "/api/voice/proof-trend-recommendations";
3122
4970
  const htmlPath = options.htmlPath === undefined ? "/voice/proof-trend-recommendations" : options.htmlPath;
3123
4971
  const markdownPath = options.markdownPath === undefined ? "/voice/proof-trend-recommendations.md" : options.markdownPath;
3124
4972
  const title = options.title ?? "Voice Provider Runtime Recommendations";
3125
- const routes = new Elysia({
4973
+ const routes = new Elysia4({
3126
4974
  name: options.name ?? "absolutejs-voice-proof-trend-recommendations"
3127
4975
  });
3128
4976
  const loadReport = async () => {
@@ -3164,7 +5012,7 @@ var createVoiceRealCallProfileHistoryRoutes = (options = {}) => {
3164
5012
  const htmlPath = options.htmlPath === undefined ? "/voice/real-call-profile-history" : options.htmlPath;
3165
5013
  const markdownPath = options.markdownPath === undefined ? "/voice/real-call-profile-history.md" : options.markdownPath;
3166
5014
  const title = options.title ?? "Voice Real-Call Profile History";
3167
- const routes = new Elysia({
5015
+ const routes = new Elysia4({
3168
5016
  name: options.name ?? "absolutejs-voice-real-call-profile-history"
3169
5017
  });
3170
5018
  const loadReport = async () => {
@@ -3216,7 +5064,7 @@ var loadVoiceRealCallProfileHistoryRouteReport = async (options) => {
3216
5064
  };
3217
5065
  var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
3218
5066
  const path = options.path ?? "/api/voice/real-call-profile-history";
3219
- const routes = new Elysia({
5067
+ const routes = new Elysia4({
3220
5068
  name: options.name ?? "absolutejs-voice-real-call-profile-recovery-actions"
3221
5069
  });
3222
5070
  const actionPath = (actionId) => `${path}${realCallProfileActionPaths[actionId]}`;
@@ -3391,7 +5239,7 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
3391
5239
  };
3392
5240
  var createVoiceProofTrendRoutes = (options) => {
3393
5241
  const path = options.path ?? "/api/voice/proof-trends";
3394
- const routes = new Elysia({
5242
+ const routes = new Elysia4({
3395
5243
  name: options.name ?? "absolutejs-voice-proof-trends"
3396
5244
  });
3397
5245
  routes.get(path, async () => {
@@ -3510,7 +5358,7 @@ var DEFAULT_LINKS2 = [
3510
5358
  { href: "/voice/proof-trends", label: "Trend page" },
3511
5359
  { href: "/api/voice/proof-trends", label: "Trend JSON" }
3512
5360
  ];
3513
- var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5361
+ var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3514
5362
  var formatMs = (value) => typeof value === "number" && Number.isFinite(value) ? `${Math.round(value)}ms` : "n/a";
3515
5363
  var statusLabel = (report) => {
3516
5364
  if (!report) {
@@ -3560,19 +5408,19 @@ var createVoiceProofTrendsViewModel = (snapshot, options = {}) => {
3560
5408
  var renderVoiceProofTrendsHTML = (snapshot, options = {}) => {
3561
5409
  const model = createVoiceProofTrendsViewModel(snapshot, options);
3562
5410
  const metrics = model.metrics.length ? `<div class="absolute-voice-proof-trends__metrics">${model.metrics.map((metric) => `<article>
3563
- <span>${escapeHtml6(metric.label)}</span>
3564
- <strong>${escapeHtml6(metric.value)}</strong>
3565
- </article>`).join("")}</div>` : `<p class="absolute-voice-proof-trends__empty">${model.error ? escapeHtml6(model.error) : "Run the sustained proof trends script to populate evidence."}</p>`;
3566
- const links = model.links.length ? `<p class="absolute-voice-proof-trends__links">${model.links.map((link) => `<a href="${escapeHtml6(link.href)}">${escapeHtml6(link.label)}</a>`).join("")}</p>` : "";
3567
- return `<section class="absolute-voice-proof-trends absolute-voice-proof-trends--${escapeHtml6(model.status)}">
5411
+ <span>${escapeHtml10(metric.label)}</span>
5412
+ <strong>${escapeHtml10(metric.value)}</strong>
5413
+ </article>`).join("")}</div>` : `<p class="absolute-voice-proof-trends__empty">${model.error ? escapeHtml10(model.error) : "Run the sustained proof trends script to populate evidence."}</p>`;
5414
+ const links = model.links.length ? `<p class="absolute-voice-proof-trends__links">${model.links.map((link) => `<a href="${escapeHtml10(link.href)}">${escapeHtml10(link.label)}</a>`).join("")}</p>` : "";
5415
+ return `<section class="absolute-voice-proof-trends absolute-voice-proof-trends--${escapeHtml10(model.status)}">
3568
5416
  <header class="absolute-voice-proof-trends__header">
3569
- <span class="absolute-voice-proof-trends__eyebrow">${escapeHtml6(model.title)}</span>
3570
- <strong class="absolute-voice-proof-trends__label">${escapeHtml6(model.label)}</strong>
5417
+ <span class="absolute-voice-proof-trends__eyebrow">${escapeHtml10(model.title)}</span>
5418
+ <strong class="absolute-voice-proof-trends__label">${escapeHtml10(model.label)}</strong>
3571
5419
  </header>
3572
- <p class="absolute-voice-proof-trends__description">${escapeHtml6(model.description)}</p>
5420
+ <p class="absolute-voice-proof-trends__description">${escapeHtml10(model.description)}</p>
3573
5421
  ${metrics}
3574
5422
  ${links}
3575
- ${model.error ? `<p class="absolute-voice-proof-trends__error">${escapeHtml6(model.error)}</p>` : ""}
5423
+ ${model.error ? `<p class="absolute-voice-proof-trends__error">${escapeHtml10(model.error)}</p>` : ""}
3576
5424
  </section>`;
3577
5425
  };
3578
5426
  var getVoiceProofTrendsCSS = () => `.absolute-voice-proof-trends{border:1px solid #99f6e4;border-radius:20px;background:#f0fdfa;color:#0f172a;padding:18px;box-shadow:0 18px 40px rgba(13,148,136,.12);font-family:inherit}.absolute-voice-proof-trends--warning,.absolute-voice-proof-trends--error{border-color:#f2a7a7;background:#fff7f4}.absolute-voice-proof-trends__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-proof-trends__eyebrow{color:#0f766e;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-proof-trends__label{font-size:24px;line-height:1}.absolute-voice-proof-trends__description,.absolute-voice-proof-trends__empty{color:#475569}.absolute-voice-proof-trends__metrics{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));margin-top:14px}.absolute-voice-proof-trends__metrics article{background:#fff;border:1px solid #ccfbf1;border-radius:16px;padding:12px}.absolute-voice-proof-trends__metrics span{color:#64748b;display:block;font-size:12px;font-weight:800;text-transform:uppercase}.absolute-voice-proof-trends__metrics strong{display:block;font-size:20px;margin-top:4px}.absolute-voice-proof-trends__links{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0 0}.absolute-voice-proof-trends__links a{border:1px solid #99f6e4;border-radius:999px;color:#0f766e;font-weight:800;padding:6px 10px;text-decoration:none}.absolute-voice-proof-trends__error{color:#9f1239;font-weight:700}`;
@@ -3780,7 +5628,7 @@ var DEFAULT_LINKS3 = [
3780
5628
  { href: "/production-readiness", label: "Readiness page" },
3781
5629
  { href: "/voice/slo-readiness-thresholds", label: "Gate thresholds" }
3782
5630
  ];
3783
- var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5631
+ var escapeHtml11 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3784
5632
  var formatExplanationValue = (value, unit) => {
3785
5633
  if (value === undefined || value === null) {
3786
5634
  return "n/a";
@@ -3821,23 +5669,23 @@ var createVoiceReadinessFailuresViewModel = (snapshot, options = {}) => {
3821
5669
  };
3822
5670
  var renderVoiceReadinessFailuresHTML = (snapshot, options = {}) => {
3823
5671
  const model = createVoiceReadinessFailuresViewModel(snapshot, options);
3824
- const failures = model.failures.length ? `<div class="absolute-voice-readiness-failures__items">${model.failures.map((failure) => `<article class="absolute-voice-readiness-failures__item absolute-voice-readiness-failures__item--${escapeHtml7(failure.status)}">
3825
- <span>${escapeHtml7(failure.status.toUpperCase())}</span>
3826
- <strong>${escapeHtml7(failure.label)}</strong>
3827
- <p>Observed ${escapeHtml7(failure.observed)} against ${escapeHtml7(failure.thresholdLabel)} ${escapeHtml7(failure.threshold)}.</p>
3828
- <p>${escapeHtml7(failure.remediation)}</p>
3829
- <p class="absolute-voice-readiness-failures__links">${failure.evidenceHref ? `<a href="${escapeHtml7(failure.evidenceHref)}">Evidence</a>` : ""}${failure.sourceHref ? `<a href="${escapeHtml7(failure.sourceHref)}">Threshold source</a>` : ""}</p>
3830
- </article>`).join("")}</div>` : `<p class="absolute-voice-readiness-failures__empty">${model.error ? escapeHtml7(model.error) : "No calibrated readiness gate explanations are open."}</p>`;
3831
- const links = model.links.length ? `<p class="absolute-voice-readiness-failures__links">${model.links.map((link) => `<a href="${escapeHtml7(link.href)}">${escapeHtml7(link.label)}</a>`).join("")}</p>` : "";
3832
- return `<section class="absolute-voice-readiness-failures absolute-voice-readiness-failures--${escapeHtml7(model.status)}">
5672
+ const failures = model.failures.length ? `<div class="absolute-voice-readiness-failures__items">${model.failures.map((failure) => `<article class="absolute-voice-readiness-failures__item absolute-voice-readiness-failures__item--${escapeHtml11(failure.status)}">
5673
+ <span>${escapeHtml11(failure.status.toUpperCase())}</span>
5674
+ <strong>${escapeHtml11(failure.label)}</strong>
5675
+ <p>Observed ${escapeHtml11(failure.observed)} against ${escapeHtml11(failure.thresholdLabel)} ${escapeHtml11(failure.threshold)}.</p>
5676
+ <p>${escapeHtml11(failure.remediation)}</p>
5677
+ <p class="absolute-voice-readiness-failures__links">${failure.evidenceHref ? `<a href="${escapeHtml11(failure.evidenceHref)}">Evidence</a>` : ""}${failure.sourceHref ? `<a href="${escapeHtml11(failure.sourceHref)}">Threshold source</a>` : ""}</p>
5678
+ </article>`).join("")}</div>` : `<p class="absolute-voice-readiness-failures__empty">${model.error ? escapeHtml11(model.error) : "No calibrated readiness gate explanations are open."}</p>`;
5679
+ const links = model.links.length ? `<p class="absolute-voice-readiness-failures__links">${model.links.map((link) => `<a href="${escapeHtml11(link.href)}">${escapeHtml11(link.label)}</a>`).join("")}</p>` : "";
5680
+ return `<section class="absolute-voice-readiness-failures absolute-voice-readiness-failures--${escapeHtml11(model.status)}">
3833
5681
  <header class="absolute-voice-readiness-failures__header">
3834
- <span class="absolute-voice-readiness-failures__eyebrow">${escapeHtml7(model.title)}</span>
3835
- <strong class="absolute-voice-readiness-failures__label">${escapeHtml7(model.label)}</strong>
5682
+ <span class="absolute-voice-readiness-failures__eyebrow">${escapeHtml11(model.title)}</span>
5683
+ <strong class="absolute-voice-readiness-failures__label">${escapeHtml11(model.label)}</strong>
3836
5684
  </header>
3837
- <p class="absolute-voice-readiness-failures__description">${escapeHtml7(model.description)}</p>
5685
+ <p class="absolute-voice-readiness-failures__description">${escapeHtml11(model.description)}</p>
3838
5686
  ${failures}
3839
5687
  ${links}
3840
- ${model.error ? `<p class="absolute-voice-readiness-failures__error">${escapeHtml7(model.error)}</p>` : ""}
5688
+ ${model.error ? `<p class="absolute-voice-readiness-failures__error">${escapeHtml11(model.error)}</p>` : ""}
3841
5689
  </section>`;
3842
5690
  };
3843
5691
  var getVoiceReadinessFailuresCSS = () => `.absolute-voice-readiness-failures{border:1px solid #fed7aa;border-radius:20px;background:#fff7ed;color:#1c1917;padding:18px;box-shadow:0 18px 40px rgba(234,88,12,.12);font-family:inherit}.absolute-voice-readiness-failures--ready{border-color:#86efac;background:#f0fdf4}.absolute-voice-readiness-failures--error{border-color:#fda4af;background:#fff1f2}.absolute-voice-readiness-failures__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-readiness-failures__eyebrow{color:#9a3412;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-readiness-failures__label{font-size:24px;line-height:1}.absolute-voice-readiness-failures__description,.absolute-voice-readiness-failures__empty{color:#57534e}.absolute-voice-readiness-failures__items{display:grid;gap:10px;margin-top:14px}.absolute-voice-readiness-failures__item{background:white;border:1px solid #fed7aa;border-radius:16px;padding:12px}.absolute-voice-readiness-failures__item--fail{border-color:#fb7185}.absolute-voice-readiness-failures__item span{color:#9a3412;display:block;font-size:12px;font-weight:900;text-transform:uppercase}.absolute-voice-readiness-failures__item strong{display:block;font-size:18px;margin-top:4px}.absolute-voice-readiness-failures__item p{margin:.45rem 0 0}.absolute-voice-readiness-failures__links{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0 0}.absolute-voice-readiness-failures__links a{border:1px solid #fdba74;border-radius:999px;color:#9a3412;font-weight:800;padding:6px 10px;text-decoration:none}.absolute-voice-readiness-failures__error{color:#9f1239;font-weight:700}`;
@@ -4054,7 +5902,7 @@ var createVoiceProviderSimulationControlsStore = (options) => {
4054
5902
  };
4055
5903
 
4056
5904
  // src/client/providerSimulationControlsWidget.ts
4057
- var escapeHtml8 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5905
+ var escapeHtml12 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4058
5906
  var formatKind = (kind) => (kind ?? "stt").toUpperCase();
4059
5907
  var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
4060
5908
  const configuredProviders = options.providers.filter((provider) => provider.configured !== false);
@@ -4074,18 +5922,18 @@ var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
4074
5922
  };
4075
5923
  var renderVoiceProviderSimulationControlsHTML = (snapshot, options) => {
4076
5924
  const model = createVoiceProviderSimulationControlsViewModel(snapshot, options);
4077
- const failureButtons = model.failureProviders.map((provider) => `<button type="button" data-voice-provider-fail="${escapeHtml8(provider.provider)}"${!model.canSimulateFailure || snapshot.isRunning ? " disabled" : ""}>Simulate ${escapeHtml8(provider.provider)} ${escapeHtml8(formatKind(options.kind))} failure</button>`).join("");
4078
- const recoveryButtons = model.providers.map((provider) => `<button type="button" data-voice-provider-recover="${escapeHtml8(provider.provider)}"${snapshot.isRunning ? " disabled" : ""}>Mark ${escapeHtml8(provider.provider)} recovered</button>`).join("");
5925
+ const failureButtons = model.failureProviders.map((provider) => `<button type="button" data-voice-provider-fail="${escapeHtml12(provider.provider)}"${!model.canSimulateFailure || snapshot.isRunning ? " disabled" : ""}>Simulate ${escapeHtml12(provider.provider)} ${escapeHtml12(formatKind(options.kind))} failure</button>`).join("");
5926
+ const recoveryButtons = model.providers.map((provider) => `<button type="button" data-voice-provider-recover="${escapeHtml12(provider.provider)}"${snapshot.isRunning ? " disabled" : ""}>Mark ${escapeHtml12(provider.provider)} recovered</button>`).join("");
4079
5927
  return `<section class="absolute-voice-provider-simulation absolute-voice-provider-simulation--${snapshot.error ? "error" : snapshot.isRunning ? "running" : "ready"}">
4080
5928
  <header class="absolute-voice-provider-simulation__header">
4081
- <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml8(model.title)}</span>
4082
- <strong class="absolute-voice-provider-simulation__label">${escapeHtml8(model.label)}</strong>
5929
+ <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml12(model.title)}</span>
5930
+ <strong class="absolute-voice-provider-simulation__label">${escapeHtml12(model.label)}</strong>
4083
5931
  </header>
4084
- <p class="absolute-voice-provider-simulation__description">${escapeHtml8(model.description)}</p>
4085
- ${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml8(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
5932
+ <p class="absolute-voice-provider-simulation__description">${escapeHtml12(model.description)}</p>
5933
+ ${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml12(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
4086
5934
  <div class="absolute-voice-provider-simulation__actions">${failureButtons}${recoveryButtons}</div>
4087
- ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml8(snapshot.error)}</p>` : ""}
4088
- ${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml8(model.resultText)}</pre>` : ""}
5935
+ ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml12(snapshot.error)}</p>` : ""}
5936
+ ${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml12(model.resultText)}</pre>` : ""}
4089
5937
  </section>`;
4090
5938
  };
4091
5939
  var bindVoiceProviderSimulationControls = (element, store) => {
@@ -4346,7 +6194,7 @@ var createVoiceProviderCapabilitiesStore = (path = "/api/provider-capabilities",
4346
6194
  // src/client/providerCapabilitiesWidget.ts
4347
6195
  var DEFAULT_TITLE7 = "Provider Capabilities";
4348
6196
  var DEFAULT_DESCRIPTION7 = "Configured, selected, and healthy voice providers for this deployment.";
4349
- var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6197
+ var escapeHtml13 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4350
6198
  var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
4351
6199
  var formatKind2 = (kind) => kind.toUpperCase();
4352
6200
  var formatStatus2 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
@@ -4401,25 +6249,25 @@ var createVoiceProviderCapabilitiesViewModel = (snapshot, options = {}) => {
4401
6249
  };
4402
6250
  var renderVoiceProviderCapabilitiesHTML = (snapshot, options = {}) => {
4403
6251
  const model = createVoiceProviderCapabilitiesViewModel(snapshot, options);
4404
- const capabilities = model.capabilities.length ? `<div class="absolute-voice-provider-capabilities__providers">${model.capabilities.map((capability) => `<article class="absolute-voice-provider-capabilities__provider absolute-voice-provider-capabilities__provider--${escapeHtml9(capability.status)}">
6252
+ const capabilities = model.capabilities.length ? `<div class="absolute-voice-provider-capabilities__providers">${model.capabilities.map((capability) => `<article class="absolute-voice-provider-capabilities__provider absolute-voice-provider-capabilities__provider--${escapeHtml13(capability.status)}">
4405
6253
  <header>
4406
- <strong>${escapeHtml9(capability.label)}</strong>
4407
- <span>${escapeHtml9(formatStatus2(capability.status))}</span>
6254
+ <strong>${escapeHtml13(capability.label)}</strong>
6255
+ <span>${escapeHtml13(formatStatus2(capability.status))}</span>
4408
6256
  </header>
4409
- <p>${escapeHtml9(capability.detail)}</p>
6257
+ <p>${escapeHtml13(capability.detail)}</p>
4410
6258
  <dl>${capability.rows.map((row) => `<div>
4411
- <dt>${escapeHtml9(row.label)}</dt>
4412
- <dd>${escapeHtml9(row.value)}</dd>
6259
+ <dt>${escapeHtml13(row.label)}</dt>
6260
+ <dd>${escapeHtml13(row.value)}</dd>
4413
6261
  </div>`).join("")}</dl>
4414
6262
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-capabilities__empty">Configure provider capabilities to see deployment coverage.</p>';
4415
- return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml9(model.status)}">
6263
+ return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml13(model.status)}">
4416
6264
  <header class="absolute-voice-provider-capabilities__header">
4417
- <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml9(model.title)}</span>
4418
- <strong class="absolute-voice-provider-capabilities__label">${escapeHtml9(model.label)}</strong>
6265
+ <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml13(model.title)}</span>
6266
+ <strong class="absolute-voice-provider-capabilities__label">${escapeHtml13(model.label)}</strong>
4419
6267
  </header>
4420
- <p class="absolute-voice-provider-capabilities__description">${escapeHtml9(model.description)}</p>
6268
+ <p class="absolute-voice-provider-capabilities__description">${escapeHtml13(model.description)}</p>
4421
6269
  ${capabilities}
4422
- ${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml9(model.error)}</p>` : ""}
6270
+ ${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml13(model.error)}</p>` : ""}
4423
6271
  </section>`;
4424
6272
  };
4425
6273
  var getVoiceProviderCapabilitiesCSS = () => `.absolute-voice-provider-capabilities{border:1px solid #bfd7ea;border-radius:20px;background:#f6fbff;color:#08131f;padding:18px;box-shadow:0 18px 40px rgba(14,51,78,.12);font-family:inherit}.absolute-voice-provider-capabilities--error,.absolute-voice-provider-capabilities--warning{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-provider-capabilities__header,.absolute-voice-provider-capabilities__provider header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-provider-capabilities__eyebrow{color:#255f85;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-provider-capabilities__label{font-size:24px;line-height:1}.absolute-voice-provider-capabilities__description,.absolute-voice-provider-capabilities__provider p,.absolute-voice-provider-capabilities__provider dt,.absolute-voice-provider-capabilities__empty{color:#405467}.absolute-voice-provider-capabilities__providers{display:grid;gap:12px;margin-top:14px}.absolute-voice-provider-capabilities__provider{background:#fff;border:1px solid #d7e7f3;border-radius:16px;padding:14px}.absolute-voice-provider-capabilities__provider--selected,.absolute-voice-provider-capabilities__provider--healthy{border-color:#86efac}.absolute-voice-provider-capabilities__provider--degraded,.absolute-voice-provider-capabilities__provider--rate-limited,.absolute-voice-provider-capabilities__provider--suppressed,.absolute-voice-provider-capabilities__provider--unconfigured{border-color:#f2a7a7}.absolute-voice-provider-capabilities__provider p{margin:10px 0}.absolute-voice-provider-capabilities__provider dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-provider-capabilities__provider div{background:#f6fbff;border:1px solid #d7e7f3;border-radius:12px;padding:8px}.absolute-voice-provider-capabilities__provider dt{font-size:12px}.absolute-voice-provider-capabilities__provider dd{font-weight:800;margin:4px 0 0}.absolute-voice-provider-capabilities__empty{margin:14px 0 0}.absolute-voice-provider-capabilities__error{color:#9f1239;font-weight:700}`;
@@ -4676,7 +6524,7 @@ function useVoiceProviderContracts(path = "/api/provider-contracts", options = {
4676
6524
  // src/client/providerContractsWidget.ts
4677
6525
  var DEFAULT_TITLE8 = "Provider Contracts";
4678
6526
  var DEFAULT_DESCRIPTION8 = "Production contract coverage for provider env, latency, fallback, streaming, and capabilities.";
4679
- var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6527
+ var escapeHtml14 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4680
6528
  var formatProvider2 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
4681
6529
  var formatStatus3 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
4682
6530
  var contractDetail = (row) => {
@@ -4720,26 +6568,26 @@ var createVoiceProviderContractsViewModel = (snapshot, options = {}) => {
4720
6568
  };
4721
6569
  var renderVoiceProviderContractsHTML = (snapshot, options = {}) => {
4722
6570
  const model = createVoiceProviderContractsViewModel(snapshot, options);
4723
- const rows = model.rows.length ? `<div class="absolute-voice-provider-contracts__rows">${model.rows.map((row) => `<article class="absolute-voice-provider-contracts__row absolute-voice-provider-contracts__row--${escapeHtml10(row.status)}">
6571
+ const rows = model.rows.length ? `<div class="absolute-voice-provider-contracts__rows">${model.rows.map((row) => `<article class="absolute-voice-provider-contracts__row absolute-voice-provider-contracts__row--${escapeHtml14(row.status)}">
4724
6572
  <header>
4725
- <strong>${escapeHtml10(row.label)}</strong>
4726
- <span>${escapeHtml10(formatStatus3(row.status))}</span>
6573
+ <strong>${escapeHtml14(row.label)}</strong>
6574
+ <span>${escapeHtml14(formatStatus3(row.status))}</span>
4727
6575
  </header>
4728
- <p>${escapeHtml10(row.detail)}</p>
4729
- ${row.remediations.length ? `<ul class="absolute-voice-provider-contracts__remediations">${row.remediations.map((remediation) => `<li>${remediation.href ? `<a href="${escapeHtml10(remediation.href)}">${escapeHtml10(remediation.label)}</a>` : `<strong>${escapeHtml10(remediation.label)}</strong>`}<span>${escapeHtml10(remediation.detail)}</span></li>`).join("")}</ul>` : ""}
6576
+ <p>${escapeHtml14(row.detail)}</p>
6577
+ ${row.remediations.length ? `<ul class="absolute-voice-provider-contracts__remediations">${row.remediations.map((remediation) => `<li>${remediation.href ? `<a href="${escapeHtml14(remediation.href)}">${escapeHtml14(remediation.label)}</a>` : `<strong>${escapeHtml14(remediation.label)}</strong>`}<span>${escapeHtml14(remediation.detail)}</span></li>`).join("")}</ul>` : ""}
4730
6578
  <dl>${row.rows.map((item) => `<div>
4731
- <dt>${escapeHtml10(item.label)}</dt>
4732
- <dd>${escapeHtml10(item.value)}</dd>
6579
+ <dt>${escapeHtml14(item.label)}</dt>
6580
+ <dd>${escapeHtml14(item.value)}</dd>
4733
6581
  </div>`).join("")}</dl>
4734
6582
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-contracts__empty">Configure provider contracts to see production coverage.</p>';
4735
- return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${escapeHtml10(model.status)}">
6583
+ return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${escapeHtml14(model.status)}">
4736
6584
  <header class="absolute-voice-provider-contracts__header">
4737
- <span class="absolute-voice-provider-contracts__eyebrow">${escapeHtml10(model.title)}</span>
4738
- <strong class="absolute-voice-provider-contracts__label">${escapeHtml10(model.label)}</strong>
6585
+ <span class="absolute-voice-provider-contracts__eyebrow">${escapeHtml14(model.title)}</span>
6586
+ <strong class="absolute-voice-provider-contracts__label">${escapeHtml14(model.label)}</strong>
4739
6587
  </header>
4740
- <p class="absolute-voice-provider-contracts__description">${escapeHtml10(model.description)}</p>
6588
+ <p class="absolute-voice-provider-contracts__description">${escapeHtml14(model.description)}</p>
4741
6589
  ${rows}
4742
- ${model.error ? `<p class="absolute-voice-provider-contracts__error">${escapeHtml10(model.error)}</p>` : ""}
6590
+ ${model.error ? `<p class="absolute-voice-provider-contracts__error">${escapeHtml14(model.error)}</p>` : ""}
4743
6591
  </section>`;
4744
6592
  };
4745
6593
  var getVoiceProviderContractsCSS = () => `.absolute-voice-provider-contracts{border:1px solid #b8dcc7;border-radius:20px;background:#f7fff9;color:#09140d;padding:18px;box-shadow:0 18px 40px rgba(21,83,45,.12);font-family:inherit}.absolute-voice-provider-contracts--error,.absolute-voice-provider-contracts--warning{border-color:#f2a7a7;background:#fff7f4}.absolute-voice-provider-contracts__header,.absolute-voice-provider-contracts__row header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-provider-contracts__eyebrow{color:#166534;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-provider-contracts__label{font-size:24px;line-height:1}.absolute-voice-provider-contracts__description,.absolute-voice-provider-contracts__row p,.absolute-voice-provider-contracts__row dt,.absolute-voice-provider-contracts__empty{color:#405448}.absolute-voice-provider-contracts__rows{display:grid;gap:12px;margin-top:14px}.absolute-voice-provider-contracts__row{background:#fff;border:1px solid #d6eadb;border-radius:16px;padding:14px}.absolute-voice-provider-contracts__row--pass{border-color:#86efac}.absolute-voice-provider-contracts__row--warn,.absolute-voice-provider-contracts__row--fail{border-color:#f2a7a7}.absolute-voice-provider-contracts__row p{margin:10px 0}.absolute-voice-provider-contracts__remediations{display:grid;gap:8px;list-style:none;margin:0 0 10px;padding:0}.absolute-voice-provider-contracts__remediations li{background:#fff7ed;border:1px solid #fed7aa;border-radius:12px;display:grid;gap:3px;padding:8px}.absolute-voice-provider-contracts__remediations a,.absolute-voice-provider-contracts__remediations strong{color:#9a3412}.absolute-voice-provider-contracts__remediations span{color:#7c2d12}.absolute-voice-provider-contracts__row dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-provider-contracts__row div{background:#f7fff9;border:1px solid #d6eadb;border-radius:12px;padding:8px}.absolute-voice-provider-contracts__row dt{font-size:12px}.absolute-voice-provider-contracts__row dd{font-weight:800;margin:4px 0 0}.absolute-voice-provider-contracts__error{color:#9f1239;font-weight:700}`;
@@ -4936,7 +6784,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
4936
6784
  // src/client/providerStatusWidget.ts
4937
6785
  var DEFAULT_TITLE9 = "Voice Providers";
4938
6786
  var DEFAULT_DESCRIPTION9 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
4939
- var escapeHtml11 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6787
+ var escapeHtml15 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4940
6788
  var formatProvider3 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
4941
6789
  var formatStatus4 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
4942
6790
  var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
@@ -4992,25 +6840,25 @@ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
4992
6840
  };
4993
6841
  var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
4994
6842
  const model = createVoiceProviderStatusViewModel(snapshot, options);
4995
- const providers = model.providers.length ? `<div class="absolute-voice-provider-status__providers">${model.providers.map((provider) => `<article class="absolute-voice-provider-status__provider absolute-voice-provider-status__provider--${escapeHtml11(provider.status)}">
6843
+ const providers = model.providers.length ? `<div class="absolute-voice-provider-status__providers">${model.providers.map((provider) => `<article class="absolute-voice-provider-status__provider absolute-voice-provider-status__provider--${escapeHtml15(provider.status)}">
4996
6844
  <header>
4997
- <strong>${escapeHtml11(provider.label)}</strong>
4998
- <span>${escapeHtml11(formatStatus4(provider.status))}</span>
6845
+ <strong>${escapeHtml15(provider.label)}</strong>
6846
+ <span>${escapeHtml15(formatStatus4(provider.status))}</span>
4999
6847
  </header>
5000
- <p>${escapeHtml11(provider.detail)}</p>
6848
+ <p>${escapeHtml15(provider.detail)}</p>
5001
6849
  <dl>${provider.rows.map((row) => `<div>
5002
- <dt>${escapeHtml11(row.label)}</dt>
5003
- <dd>${escapeHtml11(row.value)}</dd>
6850
+ <dt>${escapeHtml15(row.label)}</dt>
6851
+ <dd>${escapeHtml15(row.value)}</dd>
5004
6852
  </div>`).join("")}</dl>
5005
6853
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
5006
- return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml11(model.status)}">
6854
+ return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml15(model.status)}">
5007
6855
  <header class="absolute-voice-provider-status__header">
5008
- <span class="absolute-voice-provider-status__eyebrow">${escapeHtml11(model.title)}</span>
5009
- <strong class="absolute-voice-provider-status__label">${escapeHtml11(model.label)}</strong>
6856
+ <span class="absolute-voice-provider-status__eyebrow">${escapeHtml15(model.title)}</span>
6857
+ <strong class="absolute-voice-provider-status__label">${escapeHtml15(model.label)}</strong>
5010
6858
  </header>
5011
- <p class="absolute-voice-provider-status__description">${escapeHtml11(model.description)}</p>
6859
+ <p class="absolute-voice-provider-status__description">${escapeHtml15(model.description)}</p>
5012
6860
  ${providers}
5013
- ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml11(model.error)}</p>` : ""}
6861
+ ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml15(model.error)}</p>` : ""}
5014
6862
  </section>`;
5015
6863
  };
5016
6864
  var getVoiceProviderStatusCSS = () => `.absolute-voice-provider-status{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;box-shadow:0 18px 40px rgba(47,37,18,.12);font-family:inherit}.absolute-voice-provider-status--error,.absolute-voice-provider-status--warning{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-provider-status__header,.absolute-voice-provider-status__provider header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-provider-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-provider-status__label{font-size:24px;line-height:1}.absolute-voice-provider-status__description,.absolute-voice-provider-status__provider p,.absolute-voice-provider-status__provider dt,.absolute-voice-provider-status__empty{color:#514733}.absolute-voice-provider-status__providers{display:grid;gap:12px;margin-top:14px}.absolute-voice-provider-status__provider{background:#fff;border:1px solid #eee4d2;border-radius:16px;padding:14px}.absolute-voice-provider-status__provider--degraded,.absolute-voice-provider-status__provider--rate-limited,.absolute-voice-provider-status__provider--suppressed{border-color:#f2a7a7}.absolute-voice-provider-status__provider--recoverable{border-color:#fbbf24}.absolute-voice-provider-status__provider p{margin:10px 0}.absolute-voice-provider-status__provider dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-provider-status__provider div{background:#fffaf0;border:1px solid #eee4d2;border-radius:12px;padding:8px}.absolute-voice-provider-status__provider dt{font-size:12px}.absolute-voice-provider-status__provider dd{font-weight:800;margin:4px 0 0}.absolute-voice-provider-status__empty{margin:14px 0 0}.absolute-voice-provider-status__error{color:#9f1239;font-weight:700}`;
@@ -5239,7 +7087,7 @@ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {})
5239
7087
  // src/client/routingStatusWidget.ts
5240
7088
  var DEFAULT_TITLE10 = "Voice Routing";
5241
7089
  var DEFAULT_DESCRIPTION10 = "Latest provider routing decision from the self-hosted trace store.";
5242
- var escapeHtml12 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
7090
+ var escapeHtml16 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5243
7091
  var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
5244
7092
  var formatProviderRoutes = (routes) => routes && typeof routes === "object" ? Object.entries(routes).map(([role, provider]) => `${role}: ${formatValue(provider)}`).join(", ") || "None" : "None";
5245
7093
  var getProviderRoute = (routes, role) => routes && typeof routes === "object" ? formatValue(routes[role], "Not configured") : "Not configured";
@@ -5321,22 +7169,22 @@ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
5321
7169
  var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
5322
7170
  const model = createVoiceRoutingStatusViewModel(snapshot, options);
5323
7171
  const activeStack = model.activeStack.length ? `<div class="absolute-voice-routing-status__stack" aria-label="Active voice stack">${model.activeStack.map((item) => `<div>
5324
- <span>${escapeHtml12(item.label)}</span>
5325
- <strong>${escapeHtml12(item.value)}</strong>
7172
+ <span>${escapeHtml16(item.label)}</span>
7173
+ <strong>${escapeHtml16(item.value)}</strong>
5326
7174
  </div>`).join("")}</div>` : "";
5327
7175
  const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
5328
- <span>${escapeHtml12(row.label)}</span>
5329
- <strong>${escapeHtml12(row.value)}</strong>
7176
+ <span>${escapeHtml16(row.label)}</span>
7177
+ <strong>${escapeHtml16(row.value)}</strong>
5330
7178
  </div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
5331
- return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml12(model.status)}">
7179
+ return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml16(model.status)}">
5332
7180
  <header class="absolute-voice-routing-status__header">
5333
- <span class="absolute-voice-routing-status__eyebrow">${escapeHtml12(model.title)}</span>
5334
- <strong class="absolute-voice-routing-status__label">${escapeHtml12(model.label)}</strong>
7181
+ <span class="absolute-voice-routing-status__eyebrow">${escapeHtml16(model.title)}</span>
7182
+ <strong class="absolute-voice-routing-status__label">${escapeHtml16(model.label)}</strong>
5335
7183
  </header>
5336
- <p class="absolute-voice-routing-status__description">${escapeHtml12(model.description)}</p>
7184
+ <p class="absolute-voice-routing-status__description">${escapeHtml16(model.description)}</p>
5337
7185
  ${activeStack}
5338
7186
  ${rows}
5339
- ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml12(model.error)}</p>` : ""}
7187
+ ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml16(model.error)}</p>` : ""}
5340
7188
  </section>`;
5341
7189
  };
5342
7190
  var getVoiceRoutingStatusCSS = () => `.absolute-voice-routing-status{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;box-shadow:0 18px 40px rgba(47,37,18,.12);font-family:inherit}.absolute-voice-routing-status--error{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-routing-status__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-routing-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-routing-status__label{font-size:24px;line-height:1}.absolute-voice-routing-status__description{color:#514733;margin:12px 0 0}.absolute-voice-routing-status__stack{background:linear-gradient(135deg,#16130d,#49391f);border-radius:18px;color:#fff;display:grid;gap:8px;grid-template-columns:repeat(5,minmax(0,1fr));margin-top:14px;padding:12px}.absolute-voice-routing-status__stack div{border-left:1px solid rgba(255,255,255,.18);padding-left:10px}.absolute-voice-routing-status__stack div:first-child{border-left:0;padding-left:0}.absolute-voice-routing-status__stack span{color:#e9d9b8;display:block;font-size:11px;font-weight:800;letter-spacing:.08em;margin-bottom:5px;text-transform:uppercase}.absolute-voice-routing-status__stack strong{display:block;font-size:13px;line-height:1.25;overflow-wrap:anywhere}.absolute-voice-routing-status__grid{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin-top:14px}.absolute-voice-routing-status__grid div{background:#fff;border:1px solid #eee4d2;border-radius:14px;padding:10px 12px}.absolute-voice-routing-status__grid span{color:#655944;display:block;font-size:12px;margin-bottom:4px}.absolute-voice-routing-status__grid strong{overflow-wrap:anywhere}.absolute-voice-routing-status__empty{color:#655944;margin:14px 0 0}.absolute-voice-routing-status__error{color:#9f1239;font-weight:700}@media (max-width:760px){.absolute-voice-routing-status__stack{grid-template-columns:repeat(2,minmax(0,1fr))}.absolute-voice-routing-status__stack div{border-left:0;border-top:1px solid rgba(255,255,255,.18);padding-left:0;padding-top:8px}.absolute-voice-routing-status__stack div:first-child{border-top:0;padding-top:0}}`;
@@ -5550,8 +7398,8 @@ var createVoiceTraceTimelineStore = (path = "/api/voice-traces", options = {}) =
5550
7398
  };
5551
7399
 
5552
7400
  // src/client/agentSquadStatus.ts
5553
- var getString = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
5554
- var getPayloadString = (event, key) => getString(event.payload?.[key]);
7401
+ var getString4 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
7402
+ var getPayloadString = (event, key) => getString4(event.payload?.[key]);
5555
7403
  var eventStatus = (event) => {
5556
7404
  const status = getPayloadString(event, "status");
5557
7405
  if (status === "blocked")
@@ -5767,7 +7615,7 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
5767
7615
  var DEFAULT_TITLE11 = "Turn Latency";
5768
7616
  var DEFAULT_DESCRIPTION11 = "Per-turn timing from first transcript to commit and assistant response start.";
5769
7617
  var DEFAULT_PROOF_LABEL = "Run latency proof";
5770
- var escapeHtml13 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
7618
+ var escapeHtml17 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5771
7619
  var formatMs2 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
5772
7620
  var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
5773
7621
  const turns = (snapshot.report?.turns ?? []).map((turn) => ({
@@ -5795,25 +7643,25 @@ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
5795
7643
  };
5796
7644
  var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
5797
7645
  const model = createVoiceTurnLatencyViewModel(snapshot, options);
5798
- const turns = model.turns.length ? `<div class="absolute-voice-turn-latency__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-latency__turn absolute-voice-turn-latency__turn--${escapeHtml13(turn.status)}">
7646
+ const turns = model.turns.length ? `<div class="absolute-voice-turn-latency__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-latency__turn absolute-voice-turn-latency__turn--${escapeHtml17(turn.status)}">
5799
7647
  <header>
5800
- <strong>${escapeHtml13(turn.label)}</strong>
5801
- <span>${escapeHtml13(turn.status)}</span>
7648
+ <strong>${escapeHtml17(turn.label)}</strong>
7649
+ <span>${escapeHtml17(turn.status)}</span>
5802
7650
  </header>
5803
7651
  <dl>${turn.rows.map((row) => `<div>
5804
- <dt>${escapeHtml13(row.label)}</dt>
5805
- <dd>${escapeHtml13(row.value)}</dd>
7652
+ <dt>${escapeHtml17(row.label)}</dt>
7653
+ <dd>${escapeHtml17(row.value)}</dd>
5806
7654
  </div>`).join("")}</dl>
5807
7655
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-latency__empty">Complete a voice turn to see latency diagnostics.</p>';
5808
- return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml13(model.status)}">
7656
+ return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml17(model.status)}">
5809
7657
  <header class="absolute-voice-turn-latency__header">
5810
- <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml13(model.title)}</span>
5811
- <strong class="absolute-voice-turn-latency__label">${escapeHtml13(model.label)}</strong>
7658
+ <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml17(model.title)}</span>
7659
+ <strong class="absolute-voice-turn-latency__label">${escapeHtml17(model.label)}</strong>
5812
7660
  </header>
5813
- <p class="absolute-voice-turn-latency__description">${escapeHtml13(model.description)}</p>
5814
- ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml13(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
7661
+ <p class="absolute-voice-turn-latency__description">${escapeHtml17(model.description)}</p>
7662
+ ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml17(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
5815
7663
  ${turns}
5816
- ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml13(model.error)}</p>` : ""}
7664
+ ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml17(model.error)}</p>` : ""}
5817
7665
  </section>`;
5818
7666
  };
5819
7667
  var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {}) => {
@@ -6046,7 +7894,7 @@ var createVoiceTurnQualityStore = (path = "/api/turn-quality", options = {}) =>
6046
7894
  // src/client/turnQualityWidget.ts
6047
7895
  var DEFAULT_TITLE12 = "Turn Quality";
6048
7896
  var DEFAULT_DESCRIPTION12 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
6049
- var escapeHtml14 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
7897
+ var escapeHtml18 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6050
7898
  var formatConfidence = (value) => typeof value === "number" ? `${Math.round(value * 100)}%` : "n/a";
6051
7899
  var formatMaybe = (value) => value === undefined || value === "" ? "n/a" : String(value);
6052
7900
  var getTurnDetail = (turn) => {
@@ -6096,25 +7944,25 @@ var createVoiceTurnQualityViewModel = (snapshot, options = {}) => {
6096
7944
  };
6097
7945
  var renderVoiceTurnQualityHTML = (snapshot, options = {}) => {
6098
7946
  const model = createVoiceTurnQualityViewModel(snapshot, options);
6099
- const turns = model.turns.length ? `<div class="absolute-voice-turn-quality__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-quality__turn absolute-voice-turn-quality__turn--${escapeHtml14(turn.status)}">
7947
+ const turns = model.turns.length ? `<div class="absolute-voice-turn-quality__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-quality__turn absolute-voice-turn-quality__turn--${escapeHtml18(turn.status)}">
6100
7948
  <header>
6101
- <strong>${escapeHtml14(turn.label)}</strong>
6102
- <span>${escapeHtml14(turn.status)}</span>
7949
+ <strong>${escapeHtml18(turn.label)}</strong>
7950
+ <span>${escapeHtml18(turn.status)}</span>
6103
7951
  </header>
6104
- <p>${escapeHtml14(turn.detail)}</p>
7952
+ <p>${escapeHtml18(turn.detail)}</p>
6105
7953
  <dl>${turn.rows.map((row) => `<div>
6106
- <dt>${escapeHtml14(row.label)}</dt>
6107
- <dd>${escapeHtml14(row.value)}</dd>
7954
+ <dt>${escapeHtml18(row.label)}</dt>
7955
+ <dd>${escapeHtml18(row.value)}</dd>
6108
7956
  </div>`).join("")}</dl>
6109
7957
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-quality__empty">Complete a voice turn to see STT quality diagnostics.</p>';
6110
- return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml14(model.status)}">
7958
+ return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml18(model.status)}">
6111
7959
  <header class="absolute-voice-turn-quality__header">
6112
- <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml14(model.title)}</span>
6113
- <strong class="absolute-voice-turn-quality__label">${escapeHtml14(model.label)}</strong>
7960
+ <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml18(model.title)}</span>
7961
+ <strong class="absolute-voice-turn-quality__label">${escapeHtml18(model.label)}</strong>
6114
7962
  </header>
6115
- <p class="absolute-voice-turn-quality__description">${escapeHtml14(model.description)}</p>
7963
+ <p class="absolute-voice-turn-quality__description">${escapeHtml18(model.description)}</p>
6116
7964
  ${turns}
6117
- ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml14(model.error)}</p>` : ""}
7965
+ ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml18(model.error)}</p>` : ""}
6118
7966
  </section>`;
6119
7967
  };
6120
7968
  var getVoiceTurnQualityCSS = () => `.absolute-voice-turn-quality{border:1px solid #e4d1a3;border-radius:20px;background:#fff9eb;color:#17120a;padding:18px;box-shadow:0 18px 40px rgba(73,48,14,.12);font-family:inherit}.absolute-voice-turn-quality--error,.absolute-voice-turn-quality--warning{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-turn-quality__header,.absolute-voice-turn-quality__turn header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-turn-quality__eyebrow{color:#8a5a0a;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-turn-quality__label{font-size:24px;line-height:1}.absolute-voice-turn-quality__description,.absolute-voice-turn-quality__turn p,.absolute-voice-turn-quality__turn dt,.absolute-voice-turn-quality__empty{color:#5a4930}.absolute-voice-turn-quality__turns{display:grid;gap:12px;margin-top:14px}.absolute-voice-turn-quality__turn{background:#fff;border:1px solid #f0dfba;border-radius:16px;padding:14px}.absolute-voice-turn-quality__turn--pass{border-color:#86efac}.absolute-voice-turn-quality__turn--warn,.absolute-voice-turn-quality__turn--unknown{border-color:#fbbf24}.absolute-voice-turn-quality__turn--fail{border-color:#f2a7a7}.absolute-voice-turn-quality__turn p{margin:10px 0}.absolute-voice-turn-quality__turn dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-turn-quality__turn div{background:#fff9eb;border:1px solid #f0dfba;border-radius:12px;padding:8px}.absolute-voice-turn-quality__turn dt{font-size:12px}.absolute-voice-turn-quality__turn dd{font-weight:800;margin:4px 0 0}.absolute-voice-turn-quality__empty{margin:14px 0 0}.absolute-voice-turn-quality__error{color:#9f1239;font-weight:700}`;