@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.
@@ -1494,7 +1494,1760 @@ var useVoiceProofTrends = (path = "/api/voice/proof-trends", options = {}) => {
1494
1494
  };
1495
1495
 
1496
1496
  // src/proofTrends.ts
1497
+ import { Elysia as Elysia4 } from "elysia";
1498
+
1499
+ // src/providerDecisionTraces.ts
1500
+ import { Elysia as Elysia3 } from "elysia";
1501
+
1502
+ // src/resilienceRoutes.ts
1503
+ import { Elysia as Elysia2 } from "elysia";
1504
+
1505
+ // src/providerHealth.ts
1497
1506
  import { Elysia } from "elysia";
1507
+ var getString = (value) => typeof value === "string" ? value : undefined;
1508
+ var getNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
1509
+ var isProviderStatus = (value) => value === "success" || value === "fallback" || value === "error";
1510
+ var summarizeVoiceProviderHealth = async (input) => {
1511
+ const options = Array.isArray(input) ? { events: input } : input;
1512
+ const events = options.events ?? await options.store?.list() ?? [];
1513
+ const providers = options.providers ?? [];
1514
+ const providerSet = new Set(providers);
1515
+ const now = options.now ?? Date.now();
1516
+ const entries = new Map;
1517
+ const isAllowedProvider = (value) => typeof value === "string" && (providerSet.size === 0 || providerSet.has(value));
1518
+ const getEntry = (provider) => {
1519
+ const existing = entries.get(provider);
1520
+ if (existing) {
1521
+ return existing;
1522
+ }
1523
+ const entry = {
1524
+ elapsedCount: 0,
1525
+ elapsedTotal: 0,
1526
+ errorCount: 0,
1527
+ fallbackCount: 0,
1528
+ provider,
1529
+ rateLimited: false,
1530
+ recommended: false,
1531
+ runCount: 0,
1532
+ status: "idle",
1533
+ timeoutCount: 0
1534
+ };
1535
+ entries.set(provider, entry);
1536
+ return entry;
1537
+ };
1538
+ for (const provider of providers) {
1539
+ getEntry(provider);
1540
+ }
1541
+ const hasProviderRouterEvents = events.some((event) => event.type === "session.error" && isAllowedProvider(event.payload.provider) && isProviderStatus(event.payload.providerStatus));
1542
+ for (const event of events) {
1543
+ if (event.type === "assistant.run") {
1544
+ if (hasProviderRouterEvents) {
1545
+ continue;
1546
+ }
1547
+ const provider2 = event.payload.variantId;
1548
+ if (!isAllowedProvider(provider2)) {
1549
+ continue;
1550
+ }
1551
+ const entry2 = getEntry(provider2);
1552
+ entry2.runCount += 1;
1553
+ const elapsedMs = getNumber(event.payload.elapsedMs);
1554
+ if (elapsedMs !== undefined) {
1555
+ entry2.elapsedCount += 1;
1556
+ entry2.elapsedTotal += elapsedMs;
1557
+ }
1558
+ continue;
1559
+ }
1560
+ if (event.type !== "session.error") {
1561
+ continue;
1562
+ }
1563
+ const provider = event.payload.provider;
1564
+ if (!isAllowedProvider(provider)) {
1565
+ continue;
1566
+ }
1567
+ const providerStatus = isProviderStatus(event.payload.providerStatus) ? event.payload.providerStatus : undefined;
1568
+ const applyProviderHealth = () => {
1569
+ const entry2 = getEntry(provider);
1570
+ const providerHealth = event.payload.providerHealth;
1571
+ if (providerHealth && typeof providerHealth === "object") {
1572
+ const suppressedUntil2 = getNumber(providerHealth.suppressedUntil);
1573
+ if (suppressedUntil2 !== undefined) {
1574
+ entry2.suppressedUntil = suppressedUntil2;
1575
+ }
1576
+ }
1577
+ const suppressedUntil = getNumber(event.payload.suppressedUntil);
1578
+ if (suppressedUntil !== undefined) {
1579
+ entry2.suppressedUntil = suppressedUntil;
1580
+ }
1581
+ const suppressionRemainingMs = getNumber(event.payload.suppressionRemainingMs);
1582
+ if (suppressionRemainingMs !== undefined) {
1583
+ entry2.suppressionRemainingMs = suppressionRemainingMs;
1584
+ }
1585
+ return entry2;
1586
+ };
1587
+ if (providerStatus === "success" || providerStatus === "fallback") {
1588
+ const entry2 = applyProviderHealth();
1589
+ entry2.runCount += 1;
1590
+ entry2.lastSuccessAt = event.at;
1591
+ if (providerStatus === "success") {
1592
+ entry2.lastError = undefined;
1593
+ entry2.rateLimited = false;
1594
+ entry2.suppressedUntil = undefined;
1595
+ entry2.suppressionRemainingMs = undefined;
1596
+ }
1597
+ const elapsedMs = getNumber(event.payload.elapsedMs);
1598
+ if (elapsedMs !== undefined) {
1599
+ entry2.elapsedCount += 1;
1600
+ entry2.elapsedTotal += elapsedMs;
1601
+ }
1602
+ const selectedProvider = event.payload.selectedProvider;
1603
+ if (providerStatus === "fallback" && isAllowedProvider(selectedProvider) && selectedProvider !== provider) {
1604
+ getEntry(selectedProvider).fallbackCount += 1;
1605
+ }
1606
+ continue;
1607
+ }
1608
+ const entry = applyProviderHealth();
1609
+ entry.errorCount += 1;
1610
+ if (event.payload.timedOut === true) {
1611
+ entry.timeoutCount += 1;
1612
+ }
1613
+ entry.lastError = getString(event.payload.error);
1614
+ entry.lastErrorAt = event.at;
1615
+ entry.rateLimited ||= event.payload.rateLimited === true;
1616
+ }
1617
+ const summaries = [...entries.values()].map((entry) => {
1618
+ const hadSuppression = typeof entry.suppressedUntil === "number" || typeof entry.suppressionRemainingMs === "number";
1619
+ const suppressionRemainingMs = typeof entry.suppressedUntil === "number" ? Math.max(0, entry.suppressedUntil - now) : entry.suppressionRemainingMs;
1620
+ const activeSuppression = typeof suppressionRemainingMs === "number" && suppressionRemainingMs > 0;
1621
+ const recoverable = hadSuppression && !activeSuppression;
1622
+ const averageElapsedMs = entry.elapsedCount > 0 ? Math.round(entry.elapsedTotal / entry.elapsedCount) : undefined;
1623
+ 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";
1624
+ return {
1625
+ averageElapsedMs,
1626
+ errorCount: entry.errorCount,
1627
+ fallbackCount: entry.fallbackCount,
1628
+ lastError: entry.lastError,
1629
+ lastErrorAt: entry.lastErrorAt,
1630
+ lastSuccessAt: entry.lastSuccessAt,
1631
+ provider: entry.provider,
1632
+ rateLimited: entry.rateLimited,
1633
+ recommended: false,
1634
+ runCount: entry.runCount,
1635
+ status,
1636
+ suppressionRemainingMs: activeSuppression ? suppressionRemainingMs : undefined,
1637
+ suppressedUntil: entry.suppressedUntil,
1638
+ timeoutCount: entry.timeoutCount
1639
+ };
1640
+ });
1641
+ const recommended = summaries.filter((entry) => entry.status === "healthy").sort((left, right) => (left.averageElapsedMs ?? Number.MAX_SAFE_INTEGER) - (right.averageElapsedMs ?? Number.MAX_SAFE_INTEGER))[0];
1642
+ if (recommended) {
1643
+ recommended.recommended = true;
1644
+ }
1645
+ return summaries;
1646
+ };
1647
+ var escapeHtml5 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1648
+ var renderVoiceProviderHealthHTML = (providers) => providers.length === 0 ? '<p class="voice-provider-empty">No provider status yet.</p>' : [
1649
+ '<div class="voice-provider-health">',
1650
+ ...providers.map((provider) => {
1651
+ const suppressionSeconds = typeof provider.suppressionRemainingMs === "number" ? Math.ceil(provider.suppressionRemainingMs / 1000) : undefined;
1652
+ return [
1653
+ `<article class="voice-provider-card ${escapeHtml5(provider.status)}">`,
1654
+ '<div class="voice-provider-card-header">',
1655
+ `<strong>${escapeHtml5(provider.provider)}</strong>`,
1656
+ `<span>${escapeHtml5(provider.status)}${provider.recommended ? " \xB7 recommended" : ""}</span>`,
1657
+ "</div>",
1658
+ "<dl>",
1659
+ `<div><dt>Runs</dt><dd>${String(provider.runCount)}</dd></div>`,
1660
+ `<div><dt>Avg latency</dt><dd>${String(provider.averageElapsedMs ?? 0)}ms</dd></div>`,
1661
+ `<div><dt>Errors</dt><dd>${String(provider.errorCount)}</dd></div>`,
1662
+ `<div><dt>Timeouts</dt><dd>${String(provider.timeoutCount)}</dd></div>`,
1663
+ `<div><dt>Fallbacks</dt><dd>${String(provider.fallbackCount)}</dd></div>`,
1664
+ "</dl>",
1665
+ suppressionSeconds ? `<p>Temporarily suppressed for ${String(suppressionSeconds)}s.</p>` : "",
1666
+ provider.lastError ? `<p>${escapeHtml5(provider.lastError)}</p>` : "",
1667
+ "</article>"
1668
+ ].join("");
1669
+ }),
1670
+ "</div>"
1671
+ ].join("");
1672
+ var createVoiceProviderHealthJSONHandler = (options) => async () => summarizeVoiceProviderHealth(options);
1673
+ var createVoiceProviderHealthHTMLHandler = (options) => async () => {
1674
+ const providers = await summarizeVoiceProviderHealth(options);
1675
+ const render = options.render ?? renderVoiceProviderHealthHTML;
1676
+ const body = await render(providers);
1677
+ return new Response(body, {
1678
+ headers: {
1679
+ "Content-Type": "text/html; charset=utf-8",
1680
+ ...options.headers
1681
+ }
1682
+ });
1683
+ };
1684
+ var createVoiceProviderHealthRoutes = (options) => {
1685
+ const path = options.path ?? "/api/provider-status";
1686
+ const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
1687
+ const routes = new Elysia({
1688
+ name: options.name ?? "absolutejs-voice-provider-health"
1689
+ }).get(path, createVoiceProviderHealthJSONHandler(options));
1690
+ if (htmlPath) {
1691
+ routes.get(htmlPath, createVoiceProviderHealthHTMLHandler(options));
1692
+ }
1693
+ return routes;
1694
+ };
1695
+
1696
+ // src/resilienceRoutes.ts
1697
+ var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1698
+ var getString2 = (value) => typeof value === "string" ? value : undefined;
1699
+ var getNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
1700
+ var getBoolean = (value) => value === true;
1701
+ var isProviderStatus2 = (value) => value === "error" || value === "fallback" || value === "success";
1702
+ var listVoiceRoutingEvents = (events) => {
1703
+ const routingEvents = [];
1704
+ for (const event of events) {
1705
+ if (event.type !== "session.error") {
1706
+ continue;
1707
+ }
1708
+ const provider = getString2(event.payload.provider);
1709
+ const providerStatus = isProviderStatus2(event.payload.providerStatus) ? event.payload.providerStatus : undefined;
1710
+ if (!provider || !providerStatus) {
1711
+ continue;
1712
+ }
1713
+ const kind = getString2(event.payload.kind);
1714
+ routingEvents.push({
1715
+ at: event.at,
1716
+ attempt: getNumber2(event.payload.attempt),
1717
+ elapsedMs: getNumber2(event.payload.elapsedMs),
1718
+ error: getString2(event.payload.error),
1719
+ fallbackProvider: getString2(event.payload.fallbackProvider),
1720
+ kind: kind === "stt" || kind === "tts" ? kind : "llm",
1721
+ latencyBudgetMs: getNumber2(event.payload.latencyBudgetMs),
1722
+ operation: getString2(event.payload.operation),
1723
+ provider,
1724
+ routing: getString2(event.payload.routing),
1725
+ scenarioId: event.scenarioId,
1726
+ selectedProvider: getString2(event.payload.selectedProvider),
1727
+ sessionId: event.sessionId,
1728
+ status: providerStatus,
1729
+ suppressionRemainingMs: getNumber2(event.payload.suppressionRemainingMs),
1730
+ timedOut: getBoolean(event.payload.timedOut),
1731
+ turnId: event.turnId
1732
+ });
1733
+ }
1734
+ return routingEvents.sort((left, right) => right.at - left.at);
1735
+ };
1736
+ var summarizeVoiceRoutingDecision = (events, options = {}) => {
1737
+ const routingEvents = listVoiceRoutingEvents(events).filter((event) => {
1738
+ if (options.kind && event.kind !== options.kind) {
1739
+ return false;
1740
+ }
1741
+ if (options.sessionId && event.sessionId !== options.sessionId) {
1742
+ return false;
1743
+ }
1744
+ return true;
1745
+ });
1746
+ const limited = typeof options.limit === "number" && options.limit >= 0 ? routingEvents.slice(0, options.limit) : routingEvents;
1747
+ return limited[0] ?? null;
1748
+ };
1749
+ var createEmptyKindSummary = () => ({
1750
+ errorCount: 0,
1751
+ fallbackCount: 0,
1752
+ providers: [],
1753
+ runCount: 0,
1754
+ timeoutCount: 0
1755
+ });
1756
+ var summarizeVoiceRoutingSessions = (events, options = {}) => {
1757
+ const routingEvents = (events.some((event) => ("payload" in event)) ? listVoiceRoutingEvents(events) : [...events]).filter((event) => !options.sessionId || event.sessionId === options.sessionId);
1758
+ const sessions = new Map;
1759
+ for (const event of routingEvents) {
1760
+ const existing = sessions.get(event.sessionId);
1761
+ const summary = existing ?? {
1762
+ errorCount: 0,
1763
+ eventCount: 0,
1764
+ fallbackCount: 0,
1765
+ kinds: {
1766
+ llm: createEmptyKindSummary(),
1767
+ stt: createEmptyKindSummary(),
1768
+ tts: createEmptyKindSummary()
1769
+ },
1770
+ lastEventAt: event.at,
1771
+ sessionId: event.sessionId,
1772
+ startedAt: event.at,
1773
+ status: "healthy",
1774
+ timeoutCount: 0
1775
+ };
1776
+ summary.eventCount += 1;
1777
+ summary.startedAt = Math.min(summary.startedAt, event.at);
1778
+ summary.lastEventAt = Math.max(summary.lastEventAt, event.at);
1779
+ if (event.status === "error") {
1780
+ summary.errorCount += 1;
1781
+ }
1782
+ if (event.status === "fallback") {
1783
+ summary.fallbackCount += 1;
1784
+ }
1785
+ if (event.timedOut) {
1786
+ summary.timeoutCount += 1;
1787
+ }
1788
+ const kind = summary.kinds[event.kind];
1789
+ kind.runCount += 1;
1790
+ if (event.status === "error") {
1791
+ kind.errorCount += 1;
1792
+ }
1793
+ if (event.status === "fallback") {
1794
+ kind.fallbackCount += 1;
1795
+ }
1796
+ if (event.timedOut) {
1797
+ kind.timeoutCount += 1;
1798
+ }
1799
+ if (event.provider && !kind.providers.includes(event.provider)) {
1800
+ kind.providers.push(event.provider);
1801
+ }
1802
+ if (!kind.latest || event.at > kind.latest.at) {
1803
+ kind.latest = event;
1804
+ }
1805
+ summary.status = summary.errorCount > 0 || summary.timeoutCount > 0 ? "degraded" : summary.fallbackCount > 0 ? "fallback" : "healthy";
1806
+ sessions.set(event.sessionId, summary);
1807
+ }
1808
+ const sorted = [...sessions.values()].sort((left, right) => right.lastEventAt - left.lastEventAt);
1809
+ return typeof options.limit === "number" && options.limit >= 0 ? sorted.slice(0, options.limit) : sorted;
1810
+ };
1811
+ var createVoiceRoutingDecisionSummary = async (options) => {
1812
+ const events = await options.store.list({
1813
+ sessionId: options.sessionId,
1814
+ type: "session.error"
1815
+ });
1816
+ return summarizeVoiceRoutingDecision(events, options);
1817
+ };
1818
+ var summarizeRoutingEvents = (events) => {
1819
+ const byKind = new Map;
1820
+ let errors = 0;
1821
+ let fallbacks = 0;
1822
+ let timeouts = 0;
1823
+ for (const event of events) {
1824
+ byKind.set(event.kind, (byKind.get(event.kind) ?? 0) + 1);
1825
+ if (event.status === "error") {
1826
+ errors += 1;
1827
+ }
1828
+ if (event.status === "fallback") {
1829
+ fallbacks += 1;
1830
+ }
1831
+ if (event.timedOut) {
1832
+ timeouts += 1;
1833
+ }
1834
+ }
1835
+ return {
1836
+ byKind,
1837
+ errors,
1838
+ fallbacks,
1839
+ timeouts,
1840
+ total: events.length
1841
+ };
1842
+ };
1843
+ var renderProviderCards = (title, providers) => {
1844
+ if (providers.length === 0) {
1845
+ return `<p class="muted">No ${escapeHtml6(title)} provider health yet.</p>`;
1846
+ }
1847
+ return `<div class="provider-grid">${providers.map((provider) => `
1848
+ <article class="card provider ${escapeHtml6(provider.status)}">
1849
+ <div class="card-header">
1850
+ <strong>${escapeHtml6(provider.provider)}</strong>
1851
+ <span>${escapeHtml6(provider.status)}${provider.recommended ? " \xB7 recommended" : ""}</span>
1852
+ </div>
1853
+ <dl>
1854
+ <div><dt>Runs</dt><dd>${provider.runCount}</dd></div>
1855
+ <div><dt>Avg latency</dt><dd>${provider.averageElapsedMs ?? 0}ms</dd></div>
1856
+ <div><dt>Errors</dt><dd>${provider.errorCount}</dd></div>
1857
+ <div><dt>Timeouts</dt><dd>${provider.timeoutCount}</dd></div>
1858
+ <div><dt>Fallbacks</dt><dd>${provider.fallbackCount}</dd></div>
1859
+ </dl>
1860
+ ${provider.lastError ? `<p class="muted">${escapeHtml6(provider.lastError)}</p>` : ""}
1861
+ </article>
1862
+ `).join("")}</div>`;
1863
+ };
1864
+ var renderTimeline = (events) => {
1865
+ if (events.length === 0) {
1866
+ return '<p class="muted">No provider routing events yet. Run the app or simulate provider failover.</p>';
1867
+ }
1868
+ return `<div class="timeline">${events.slice(0, 40).map((event) => `
1869
+ <article class="card event ${escapeHtml6(event.status ?? "unknown")}">
1870
+ <div class="card-header">
1871
+ <strong>${escapeHtml6(event.kind.toUpperCase())} ${escapeHtml6(event.operation ?? "generate")}</strong>
1872
+ <span>${new Date(event.at).toLocaleString()}</span>
1873
+ </div>
1874
+ <p>
1875
+ <span class="pill">${escapeHtml6(event.status ?? "unknown")}</span>
1876
+ <span class="pill">provider: ${escapeHtml6(event.provider ?? "unknown")}</span>
1877
+ ${event.fallbackProvider ? `<span class="pill">fallback: ${escapeHtml6(event.fallbackProvider)}</span>` : ""}
1878
+ ${event.timedOut ? '<span class="pill danger">timed out</span>' : ""}
1879
+ </p>
1880
+ <dl>
1881
+ <div><dt>Attempt</dt><dd>${event.attempt ?? 0}</dd></div>
1882
+ <div><dt>Elapsed</dt><dd>${event.elapsedMs ?? 0}ms</dd></div>
1883
+ <div><dt>Budget</dt><dd>${event.latencyBudgetMs ?? 0}ms</dd></div>
1884
+ <div><dt>Session</dt><dd>${escapeHtml6(event.sessionId)}</dd></div>
1885
+ </dl>
1886
+ ${event.error ? `<p class="muted">${escapeHtml6(event.error)}</p>` : ""}
1887
+ </article>
1888
+ `).join("")}</div>`;
1889
+ };
1890
+ var renderSessionKind = (kind, summary) => {
1891
+ const latest = summary.latest;
1892
+ const provider = latest?.provider ?? summary.providers[0] ?? "none";
1893
+ const status = latest?.status ?? "idle";
1894
+ const fallback = latest?.fallbackProvider && latest.fallbackProvider !== provider ? ` -> ${latest.fallbackProvider}` : "";
1895
+ return `<div>
1896
+ <dt>${escapeHtml6(kind.toUpperCase())}</dt>
1897
+ <dd>${escapeHtml6(provider)}${escapeHtml6(fallback)}</dd>
1898
+ <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>
1899
+ </div>`;
1900
+ };
1901
+ var renderSessionSummaries = (sessions) => {
1902
+ if (sessions.length === 0) {
1903
+ return '<p class="muted">No call-level routing summaries yet. Run a voice session or provider simulation.</p>';
1904
+ }
1905
+ return `<div class="session-grid">${sessions.slice(0, 12).map((session) => `
1906
+ <article class="card session ${escapeHtml6(session.status)}">
1907
+ <div class="card-header">
1908
+ <strong>${escapeHtml6(session.sessionId)}</strong>
1909
+ <span>${escapeHtml6(session.status)}</span>
1910
+ </div>
1911
+ <p>
1912
+ <span class="pill">${session.eventCount} routing events</span>
1913
+ <span class="pill">${session.fallbackCount} fallbacks</span>
1914
+ <span class="pill">${session.errorCount} errors</span>
1915
+ <span class="pill">${session.timeoutCount} timeouts</span>
1916
+ </p>
1917
+ <dl>
1918
+ ${renderSessionKind("llm", session.kinds.llm)}
1919
+ ${renderSessionKind("stt", session.kinds.stt)}
1920
+ ${renderSessionKind("tts", session.kinds.tts)}
1921
+ </dl>
1922
+ </article>
1923
+ `).join("")}</div>`;
1924
+ };
1925
+ var renderSimulationControls = (kind, simulation) => {
1926
+ if (!simulation) {
1927
+ return "";
1928
+ }
1929
+ const configuredProviders = simulation.providers.filter((provider) => provider.configured !== false);
1930
+ if (configuredProviders.length === 0) {
1931
+ return `<p class="muted">No ${kind.toUpperCase()} providers are configured for simulation.</p>`;
1932
+ }
1933
+ const pathPrefix = simulation.pathPrefix ?? `/api/${kind}-simulate`;
1934
+ const failureProviders = simulation.failureProviders ?? configuredProviders.map(({ provider }) => provider);
1935
+ const canFail = (provider) => configuredProviders.some((entry) => entry.provider === provider) && (!simulation.fallbackRequiredProvider || configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider));
1936
+ return `<div class="simulate-panel" data-sim-kind="${kind}" data-sim-prefix="${escapeHtml6(pathPrefix)}">
1937
+ <p class="muted">${escapeHtml6(simulation.failureMessage ?? `Simulate ${kind.toUpperCase()} provider failure without changing provider credentials.`)}</p>
1938
+ <div class="simulate-actions">
1939
+ ${failureProviders.map((provider) => `<button type="button" data-provider-fail="${escapeHtml6(provider)}"${canFail(provider) ? "" : " disabled"}>Simulate ${escapeHtml6(provider)} ${kind.toUpperCase()} failure</button>`).join("")}
1940
+ ${configuredProviders.map((provider) => `<button type="button" data-provider-recover="${escapeHtml6(provider.provider)}">Mark ${escapeHtml6(provider.provider)} recovered</button>`).join("")}
1941
+ </div>
1942
+ ${simulation.fallbackRequiredProvider && !configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider) ? `<p class="muted">${escapeHtml6(simulation.fallbackRequiredMessage ?? `Configure ${simulation.fallbackRequiredProvider} to enable fallback simulation.`)}</p>` : ""}
1943
+ <pre class="simulate-output" hidden></pre>
1944
+ </div>`;
1945
+ };
1946
+ var renderVoiceResilienceHTML = (input) => {
1947
+ const summary = summarizeRoutingEvents(input.routingEvents);
1948
+ const kindCounts = [...summary.byKind.entries()].map(([kind, count]) => `<span class="pill">${escapeHtml6(kind)}: ${String(count)}</span>`).join("");
1949
+ const links = input.links?.length ? input.links.map((link) => `<a href="${escapeHtml6(link.href)}">${escapeHtml6(link.label)}</a>`).join(" \xB7 ") : "";
1950
+ const snippet = escapeHtml6(`const sttSimulator = createVoiceIOProviderFailureSimulator({
1951
+ kind: 'stt',
1952
+ providers: ['deepgram', 'assemblyai'],
1953
+ fallback: ['deepgram', 'assemblyai'],
1954
+ onProviderEvent: async (event, input) => {
1955
+ await traceStore.append({
1956
+ at: event.at,
1957
+ payload: { ...event, providerStatus: event.status },
1958
+ sessionId: input.sessionId,
1959
+ type: 'session.error'
1960
+ });
1961
+ }
1962
+ });
1963
+
1964
+ app.use(
1965
+ createVoiceResilienceRoutes({
1966
+ store: traceStore,
1967
+ sttProviders: ['deepgram', 'assemblyai'],
1968
+ sttSimulation: {
1969
+ failureProviders: ['deepgram'],
1970
+ fallbackRequiredProvider: 'assemblyai',
1971
+ providers: [{ provider: 'deepgram' }, { provider: 'assemblyai' }],
1972
+ run: sttSimulator.run
1973
+ }
1974
+ })
1975
+ );
1976
+
1977
+ app.use(
1978
+ createVoiceProductionReadinessRoutes({
1979
+ links: { resilience: '/resilience' },
1980
+ store: traceStore
1981
+ })
1982
+ );`);
1983
+ return `<!doctype html>
1984
+ <html lang="en">
1985
+ <head>
1986
+ <meta charset="utf-8" />
1987
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
1988
+ <title>${escapeHtml6(input.title ?? "AbsoluteJS Voice Resilience")}</title>
1989
+ <style>
1990
+ :root { color-scheme: dark; }
1991
+ 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; }
1992
+ main { display: grid; gap: 16px; margin: 0 auto; max-width: 1180px; }
1993
+ section, .card { background: rgba(19, 22, 27, 0.92); border: 1px solid #27272a; border-radius: 20px; padding: 20px; }
1994
+ .hero { background: linear-gradient(135deg, rgba(14, 165, 233, 0.18), rgba(245, 158, 11, 0.12)); }
1995
+ .grid, .provider-grid { display: grid; gap: 14px; grid-template-columns: repeat(4, minmax(0, 1fr)); }
1996
+ .session-grid { display: grid; gap: 14px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
1997
+ .timeline { display: grid; gap: 12px; }
1998
+ .card-header { align-items: center; display: flex; gap: 12px; justify-content: space-between; }
1999
+ .card-header strong { font-size: 1.05rem; }
2000
+ .metric strong { display: block; font-size: 2rem; margin-top: 6px; }
2001
+ .muted, dt, span { color: #a1a1aa; }
2002
+ dl { display: grid; gap: 8px; grid-template-columns: repeat(4, minmax(0, 1fr)); }
2003
+ dl div { background: #0f1217; border: 1px solid #27272a; border-radius: 12px; padding: 10px; }
2004
+ dd { font-weight: 800; margin: 4px 0 0; }
2005
+ .pill { background: #0f1217; border: 1px solid #3f3f46; border-radius: 999px; color: #d4d4d8; display: inline-flex; margin: 3px 4px 3px 0; padding: 5px 9px; }
2006
+ .danger { border-color: rgba(239, 68, 68, 0.75); color: #fecaca; }
2007
+ .event.error { border-color: rgba(239, 68, 68, 0.7); }
2008
+ .event.fallback, .session.fallback { border-color: rgba(245, 158, 11, 0.7); }
2009
+ .event.success, .provider.healthy, .session.healthy { border-color: rgba(34, 197, 94, 0.5); }
2010
+ .session.degraded { border-color: rgba(239, 68, 68, 0.7); }
2011
+ .provider.suppressed, .provider.degraded, .provider.rate-limited { border-color: rgba(239, 68, 68, 0.7); }
2012
+ .provider.recoverable { border-color: rgba(59, 130, 246, 0.7); }
2013
+ button { background: #f59e0b; border: 0; border-radius: 999px; color: #111827; cursor: pointer; font-weight: 800; padding: 10px 14px; }
2014
+ button:disabled { cursor: not-allowed; opacity: 0.45; }
2015
+ .simulate-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
2016
+ .simulate-output { background: #050505; border: 1px solid #27272a; border-radius: 14px; color: #d4d4d8; overflow: auto; padding: 12px; white-space: pre-wrap; }
2017
+ .primitive { border-color: rgba(245, 158, 11, 0.45); }
2018
+ .primitive pre { background: #050505; border: 1px solid #27272a; border-radius: 14px; color: #fef3c7; overflow: auto; padding: 14px; }
2019
+ .primitive code { color: #fef3c7; }
2020
+ a { color: #f59e0b; }
2021
+ @media (max-width: 850px) { .grid, .provider-grid, .session-grid, dl { grid-template-columns: 1fr; } }
2022
+ </style>
2023
+ </head>
2024
+ <body>
2025
+ <main>
2026
+ <section class="hero">
2027
+ <h1>Provider routing and resilience</h1>
2028
+ <p>One view for the production reliability story: LLM failover, STT/TTS routing, latency budgets, timeouts, and fallback decisions.</p>
2029
+ ${links ? `<p>${links}</p>` : ""}
2030
+ <p>${kindCounts || '<span class="pill">No routing events yet</span>'}</p>
2031
+ </section>
2032
+ <section class="primitive">
2033
+ <p class="muted">Copy into your app</p>
2034
+ <h2><code>createVoiceResilienceRoutes(...)</code> builds this failover proof surface</h2>
2035
+ <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>
2036
+ <pre><code>${snippet}</code></pre>
2037
+ </section>
2038
+ <section class="grid">
2039
+ <article class="card metric"><span>Total routing events</span><strong>${summary.total}</strong></article>
2040
+ <article class="card metric"><span>Fallbacks</span><strong>${summary.fallbacks}</strong></article>
2041
+ <article class="card metric"><span>Errors</span><strong>${summary.errors}</strong></article>
2042
+ <article class="card metric"><span>Timeouts</span><strong>${summary.timeouts}</strong></article>
2043
+ </section>
2044
+ <section>
2045
+ <h2>Call-level routing summaries</h2>
2046
+ <p class="muted">A compact per-call view of which LLM, STT, and TTS providers handled the session, including fallback and timeout counts.</p>
2047
+ ${renderSessionSummaries(input.routingSessions)}
2048
+ </section>
2049
+ <section>
2050
+ <h2>LLM provider health</h2>
2051
+ ${renderProviderCards("LLM", input.llmProviderHealth)}
2052
+ </section>
2053
+ <section>
2054
+ <h2>STT provider health</h2>
2055
+ ${renderSimulationControls("stt", input.sttSimulation)}
2056
+ ${renderProviderCards("STT", input.sttProviderHealth)}
2057
+ </section>
2058
+ <section>
2059
+ <h2>TTS provider health</h2>
2060
+ ${renderSimulationControls("tts", input.ttsSimulation)}
2061
+ ${renderProviderCards("TTS", input.ttsProviderHealth)}
2062
+ </section>
2063
+ <section>
2064
+ <h2>Routing timeline</h2>
2065
+ ${renderTimeline(input.routingEvents)}
2066
+ </section>
2067
+ </main>
2068
+ <script>
2069
+ const showResult = (panel, result) => {
2070
+ const output = panel.querySelector(".simulate-output");
2071
+ if (!output) return;
2072
+ output.hidden = false;
2073
+ output.textContent = JSON.stringify(result, null, 2);
2074
+ };
2075
+ document.querySelectorAll("[data-sim-prefix]").forEach((panel) => {
2076
+ const prefix = panel.getAttribute("data-sim-prefix");
2077
+ panel.querySelectorAll("[data-provider-fail]").forEach((button) => {
2078
+ button.addEventListener("click", async () => {
2079
+ const provider = button.getAttribute("data-provider-fail");
2080
+ const response = await fetch(prefix + "/failure?provider=" + encodeURIComponent(provider || ""), { method: "POST" });
2081
+ showResult(panel, await response.json());
2082
+ if (response.ok) window.setTimeout(() => window.location.reload(), 450);
2083
+ });
2084
+ });
2085
+ panel.querySelectorAll("[data-provider-recover]").forEach((button) => {
2086
+ button.addEventListener("click", async () => {
2087
+ const provider = button.getAttribute("data-provider-recover");
2088
+ const response = await fetch(prefix + "/recovery?provider=" + encodeURIComponent(provider || ""), { method: "POST" });
2089
+ showResult(panel, await response.json());
2090
+ if (response.ok) window.setTimeout(() => window.location.reload(), 450);
2091
+ });
2092
+ });
2093
+ });
2094
+ </script>
2095
+ </body>
2096
+ </html>`;
2097
+ };
2098
+ var providerFromQuery = (value, providers) => typeof value === "string" && providers.some((provider) => provider.provider === value && provider.configured !== false) ? value : undefined;
2099
+ var registerSimulationRoutes = (routes, simulation, defaultPathPrefix) => {
2100
+ if (!simulation) {
2101
+ return routes;
2102
+ }
2103
+ const pathPrefix = simulation.pathPrefix ?? defaultPathPrefix;
2104
+ routes.post(`${pathPrefix}/failure`, async ({ query, set }) => {
2105
+ const provider = providerFromQuery(query.provider, simulation.providers);
2106
+ if (!provider) {
2107
+ set.status = 400;
2108
+ return {
2109
+ error: "Provider is not configured for simulation."
2110
+ };
2111
+ }
2112
+ if (simulation.failureProviders && !simulation.failureProviders.includes(provider)) {
2113
+ set.status = 400;
2114
+ return {
2115
+ error: `${provider} is not configured for failure simulation.`
2116
+ };
2117
+ }
2118
+ if (simulation.fallbackRequiredProvider && !simulation.providers.some((entry) => entry.provider === simulation.fallbackRequiredProvider && entry.configured !== false)) {
2119
+ set.status = 400;
2120
+ return {
2121
+ error: simulation.fallbackRequiredMessage ?? `Configure ${simulation.fallbackRequiredProvider} before simulating fallback.`
2122
+ };
2123
+ }
2124
+ return simulation.run(provider, "failure");
2125
+ });
2126
+ routes.post(`${pathPrefix}/recovery`, async ({ query, set }) => {
2127
+ const provider = providerFromQuery(query.provider, simulation.providers);
2128
+ if (!provider) {
2129
+ set.status = 400;
2130
+ return {
2131
+ error: "Provider is not configured for simulation."
2132
+ };
2133
+ }
2134
+ return simulation.run(provider, "recovery");
2135
+ });
2136
+ return routes;
2137
+ };
2138
+ var createVoiceResilienceRoutes = (options) => {
2139
+ const path = options.path ?? "/resilience";
2140
+ const routes = new Elysia2({
2141
+ name: options.name ?? "absolutejs-voice-resilience"
2142
+ }).get(path, async () => {
2143
+ const events = await options.store.list();
2144
+ const sttEvents = events.filter((event) => event.payload.kind === "stt");
2145
+ const ttsEvents = events.filter((event) => event.payload.kind === "tts");
2146
+ const routingEvents = listVoiceRoutingEvents(events);
2147
+ const data = {
2148
+ links: options.links,
2149
+ llmProviderHealth: await summarizeVoiceProviderHealth({
2150
+ events,
2151
+ providers: options.llmProviders ?? []
2152
+ }),
2153
+ routingEvents,
2154
+ routingSessions: summarizeVoiceRoutingSessions(routingEvents),
2155
+ sttProviderHealth: await summarizeVoiceProviderHealth({
2156
+ events: sttEvents,
2157
+ providers: options.sttProviders ?? []
2158
+ }),
2159
+ sttSimulation: options.sttSimulation,
2160
+ title: options.title,
2161
+ ttsProviderHealth: await summarizeVoiceProviderHealth({
2162
+ events: ttsEvents,
2163
+ providers: options.ttsProviders ?? []
2164
+ }),
2165
+ ttsSimulation: options.ttsSimulation
2166
+ };
2167
+ const body = await (options.render ?? renderVoiceResilienceHTML)(data);
2168
+ return new Response(body, {
2169
+ headers: {
2170
+ "Content-Type": "text/html; charset=utf-8",
2171
+ ...options.headers
2172
+ }
2173
+ });
2174
+ });
2175
+ registerSimulationRoutes(routes, options.sttSimulation, "/api/stt-simulate");
2176
+ registerSimulationRoutes(routes, options.ttsSimulation, "/api/tts-simulate");
2177
+ return routes;
2178
+ };
2179
+
2180
+ // src/providerDecisionTraces.ts
2181
+ var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2182
+ var getString3 = (value) => typeof value === "string" ? value : undefined;
2183
+ var getNumber3 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
2184
+ var isDecisionTrace = (event) => Boolean(event && typeof event === "object" && "provider" in event && "reason" in event && "sessionId" in event && "status" in event && "surface" in event);
2185
+ var surfaceForKind = (kind) => {
2186
+ switch (kind) {
2187
+ case "stt":
2188
+ return "live-stt";
2189
+ case "tts":
2190
+ return "telephony-tts";
2191
+ case "llm":
2192
+ default:
2193
+ return "live-call";
2194
+ }
2195
+ };
2196
+ var statusRank = { fail: 2, pass: 0, warn: 1 };
2197
+ var reportStatus = (issues) => issues.reduce((status, issue) => statusRank[issue.status] > statusRank[status] ? issue.status : status, "pass");
2198
+ var uniqueSorted = (values) => [
2199
+ ...new Set(values.filter((value) => typeof value === "string"))
2200
+ ].sort();
2201
+ var createVoiceProviderDecisionTraceEvent = (input) => {
2202
+ const surface = input.surface ?? surfaceForKind(input.kind);
2203
+ 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.`);
2204
+ return {
2205
+ at: input.at ?? Date.now(),
2206
+ payload: {
2207
+ ...input,
2208
+ providerDecision: true,
2209
+ reason,
2210
+ surface
2211
+ },
2212
+ scenarioId: input.scenarioId,
2213
+ sessionId: input.sessionId ?? `${surface}-provider-decision`,
2214
+ turnId: input.turnId,
2215
+ type: "provider.decision"
2216
+ };
2217
+ };
2218
+ var listVoiceProviderDecisionTraces = (events) => {
2219
+ if (events.every(isDecisionTrace)) {
2220
+ return [...events].sort((left, right) => right.at - left.at);
2221
+ }
2222
+ const traceEvents = events;
2223
+ const explicit = traceEvents.filter((event) => event.type === "provider.decision").map((event) => {
2224
+ const provider = getString3(event.payload.provider);
2225
+ const status = getString3(event.payload.status);
2226
+ const surface = getString3(event.payload.surface);
2227
+ if (!provider || !surface || status !== "error" && status !== "fallback" && status !== "degraded" && status !== "selected" && status !== "skipped" && status !== "success") {
2228
+ return;
2229
+ }
2230
+ return {
2231
+ at: event.at,
2232
+ elapsedMs: getNumber3(event.payload.elapsedMs),
2233
+ error: getString3(event.payload.error),
2234
+ fallbackProvider: getString3(event.payload.fallbackProvider),
2235
+ kind: event.payload.kind === "llm" || event.payload.kind === "stt" || event.payload.kind === "tts" ? event.payload.kind : undefined,
2236
+ latencyBudgetMs: getNumber3(event.payload.latencyBudgetMs),
2237
+ provider,
2238
+ reason: getString3(event.payload.reason) ?? `Provider ${provider} emitted ${status}.`,
2239
+ scenarioId: event.scenarioId,
2240
+ selectedProvider: getString3(event.payload.selectedProvider),
2241
+ sessionId: event.sessionId,
2242
+ status,
2243
+ surface,
2244
+ turnId: event.turnId
2245
+ };
2246
+ }).filter((event) => Boolean(event));
2247
+ const routing = listVoiceRoutingEvents(traceEvents).map((event) => ({
2248
+ at: event.at,
2249
+ elapsedMs: event.elapsedMs,
2250
+ error: event.error,
2251
+ fallbackProvider: event.fallbackProvider,
2252
+ kind: event.kind,
2253
+ latencyBudgetMs: event.latencyBudgetMs,
2254
+ provider: event.provider ?? event.selectedProvider ?? "unknown",
2255
+ 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.`,
2256
+ scenarioId: event.scenarioId,
2257
+ selectedProvider: event.selectedProvider,
2258
+ sessionId: event.sessionId,
2259
+ status: event.status === "fallback" || event.status === "error" ? event.status : "success",
2260
+ surface: getString3(event.surface) ?? surfaceForKind(event.kind),
2261
+ turnId: event.turnId
2262
+ }));
2263
+ return [...explicit, ...routing].sort((left, right) => right.at - left.at);
2264
+ };
2265
+ var buildVoiceProviderDecisionTraceReport = async (options) => {
2266
+ const now = options.now ?? Date.now();
2267
+ const rawEvents = options.events ?? await options.store?.list() ?? [];
2268
+ const decisions = listVoiceProviderDecisionTraces(rawEvents).filter((decision) => {
2269
+ if (options.sessionId && decision.sessionId !== options.sessionId) {
2270
+ return false;
2271
+ }
2272
+ if (options.maxAgeMs !== undefined && now - decision.at > options.maxAgeMs) {
2273
+ return false;
2274
+ }
2275
+ return true;
2276
+ });
2277
+ const surfaces = new Map;
2278
+ const issues = [];
2279
+ for (const decision of decisions) {
2280
+ const group = surfaces.get(decision.surface) ?? [];
2281
+ group.push(decision);
2282
+ surfaces.set(decision.surface, group);
2283
+ }
2284
+ for (const surface of options.requiredSurfaces ?? []) {
2285
+ if (!surfaces.has(surface)) {
2286
+ issues.push({
2287
+ code: "voice.provider_decision_trace.surface_missing",
2288
+ message: `Surface ${surface} has no provider decision traces.`,
2289
+ status: "fail",
2290
+ surface
2291
+ });
2292
+ }
2293
+ }
2294
+ const fallbackCount = decisions.filter((decision) => decision.status === "fallback").length;
2295
+ const degradedCount = decisions.filter((decision) => decision.status === "degraded").length;
2296
+ const statuses = new Set(decisions.map((decision) => decision.status));
2297
+ const providers = uniqueSorted(decisions.flatMap((decision) => [
2298
+ decision.provider,
2299
+ decision.selectedProvider,
2300
+ decision.fallbackProvider
2301
+ ]));
2302
+ const fallbackProviders = uniqueSorted(decisions.flatMap((decision) => [
2303
+ decision.fallbackProvider,
2304
+ decision.status === "fallback" || decision.status === "degraded" ? decision.selectedProvider : undefined
2305
+ ]));
2306
+ if (options.minDecisions !== undefined && decisions.length < options.minDecisions) {
2307
+ issues.push({
2308
+ code: "voice.provider_decision_trace.min_decisions",
2309
+ message: `Found ${String(decisions.length)} provider decision trace(s); expected at least ${String(options.minDecisions)}.`,
2310
+ status: "fail"
2311
+ });
2312
+ }
2313
+ if (options.minFallbacks !== undefined && fallbackCount < options.minFallbacks) {
2314
+ issues.push({
2315
+ code: "voice.provider_decision_trace.min_fallbacks",
2316
+ message: `Found ${String(fallbackCount)} provider fallback trace(s); expected at least ${String(options.minFallbacks)}.`,
2317
+ status: "fail"
2318
+ });
2319
+ }
2320
+ if (options.minDegraded !== undefined && degradedCount < options.minDegraded) {
2321
+ issues.push({
2322
+ code: "voice.provider_decision_trace.min_degraded",
2323
+ message: `Found ${String(degradedCount)} provider degradation trace(s); expected at least ${String(options.minDegraded)}.`,
2324
+ status: "fail"
2325
+ });
2326
+ }
2327
+ for (const status of options.requiredStatuses ?? []) {
2328
+ if (!statuses.has(status)) {
2329
+ issues.push({
2330
+ code: "voice.provider_decision_trace.status_missing",
2331
+ message: `Missing provider decision status: ${status}.`,
2332
+ status: "fail"
2333
+ });
2334
+ }
2335
+ }
2336
+ for (const provider of options.requiredProviders ?? []) {
2337
+ if (!providers.includes(provider)) {
2338
+ issues.push({
2339
+ code: "voice.provider_decision_trace.provider_missing",
2340
+ message: `Missing provider decision provider: ${provider}.`,
2341
+ status: "fail"
2342
+ });
2343
+ }
2344
+ }
2345
+ for (const provider of options.requiredFallbackProviders ?? []) {
2346
+ if (!fallbackProviders.includes(provider)) {
2347
+ issues.push({
2348
+ code: "voice.provider_decision_trace.fallback_provider_missing",
2349
+ message: `Missing provider decision fallback provider: ${provider}.`,
2350
+ status: "fail"
2351
+ });
2352
+ }
2353
+ }
2354
+ for (const phrase of options.requiredReasonIncludes ?? []) {
2355
+ if (!decisions.some((decision) => decision.reason.includes(phrase))) {
2356
+ issues.push({
2357
+ code: "voice.provider_decision_trace.reason_missing",
2358
+ message: `Missing provider decision reason containing: ${phrase}.`,
2359
+ status: "fail"
2360
+ });
2361
+ }
2362
+ }
2363
+ const surfaceReports = [...surfaces.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([surface, surfaceDecisions]) => {
2364
+ const surfaceIssues = issues.filter((issue) => issue.surface === surface);
2365
+ return {
2366
+ degraded: surfaceDecisions.filter((decision) => decision.status === "degraded").length,
2367
+ decisions: surfaceDecisions.length,
2368
+ errors: surfaceDecisions.filter((decision) => decision.status === "error").length,
2369
+ fallbacks: surfaceDecisions.filter((decision) => decision.status === "fallback").length,
2370
+ issues: surfaceIssues,
2371
+ latestAt: Math.max(...surfaceDecisions.map((decision) => decision.at)),
2372
+ providers: uniqueSorted(surfaceDecisions.flatMap((decision) => [
2373
+ decision.provider,
2374
+ decision.selectedProvider,
2375
+ decision.fallbackProvider
2376
+ ])),
2377
+ reasons: uniqueSorted(surfaceDecisions.map((decision) => decision.reason)),
2378
+ selected: surfaceDecisions.filter((decision) => decision.status === "selected" || decision.status === "success").length,
2379
+ status: reportStatus(surfaceIssues),
2380
+ surface
2381
+ };
2382
+ });
2383
+ return {
2384
+ checkedAt: now,
2385
+ decisions,
2386
+ issues,
2387
+ status: reportStatus(issues),
2388
+ summary: {
2389
+ degraded: degradedCount,
2390
+ decisions: decisions.length,
2391
+ errors: decisions.filter((decision) => decision.status === "error").length,
2392
+ fallbacks: fallbackCount,
2393
+ providers: providers.length,
2394
+ selected: decisions.filter((decision) => decision.status === "selected" || decision.status === "success").length,
2395
+ surfaces: surfaces.size
2396
+ },
2397
+ surfaces: surfaceReports
2398
+ };
2399
+ };
2400
+ var renderVoiceProviderDecisionTraceMarkdown = (report) => [
2401
+ "# Voice Provider Decision Traces",
2402
+ "",
2403
+ `Status: **${report.status}**`,
2404
+ `Decisions: ${String(report.summary.decisions)}`,
2405
+ `Providers: ${String(report.summary.providers)}`,
2406
+ `Fallbacks: ${String(report.summary.fallbacks)}`,
2407
+ `Degraded: ${String(report.summary.degraded)}`,
2408
+ `Errors: ${String(report.summary.errors)}`,
2409
+ "",
2410
+ "| Surface | Status | Decisions | Selected | Fallbacks | Degraded | Errors | Providers |",
2411
+ "| --- | --- | ---: | ---: | ---: | ---: | ---: | --- |",
2412
+ ...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(", ")} |`),
2413
+ "",
2414
+ ...report.issues.map((issue) => `- ${issue.status}: ${issue.message}`)
2415
+ ].join(`
2416
+ `);
2417
+ var renderVoiceProviderDecisionTraceHTML = (report, title = "Provider Decision Traces") => `<!doctype html>
2418
+ <html lang="en">
2419
+ <head>
2420
+ <meta charset="utf-8" />
2421
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
2422
+ <title>${escapeHtml7(title)}</title>
2423
+ <style>
2424
+ body{font-family:ui-sans-serif,system-ui,sans-serif;margin:0;background:#f8fafc;color:#0f172a}
2425
+ main{max-width:1100px;margin:0 auto;padding:32px}
2426
+ .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px}
2427
+ .card,.surface{background:white;border:1px solid #e2e8f0;border-radius:16px;padding:16px;box-shadow:0 12px 30px rgba(15,23,42,.06)}
2428
+ .status{display:inline-flex;border-radius:999px;padding:4px 10px;font-weight:700;background:#dcfce7;color:#166534}
2429
+ .status.fail{background:#fee2e2;color:#991b1b}.status.warn{background:#fef3c7;color:#92400e}
2430
+ .surfaces{display:grid;gap:14px;margin-top:18px}.muted{color:#64748b}
2431
+ code{background:#e2e8f0;border-radius:8px;padding:2px 6px}
2432
+ </style>
2433
+ </head>
2434
+ <body>
2435
+ <main>
2436
+ <p class="status ${report.status}">${escapeHtml7(report.status)}</p>
2437
+ <h1>${escapeHtml7(title)}</h1>
2438
+ <p class="muted">Runtime proof for why providers were selected, skipped, failed, or recovered by fallback.</p>
2439
+ <section class="grid">
2440
+ <article class="card"><strong>${String(report.summary.decisions)}</strong><p>decisions</p></article>
2441
+ <article class="card"><strong>${String(report.summary.providers)}</strong><p>providers</p></article>
2442
+ <article class="card"><strong>${String(report.summary.fallbacks)}</strong><p>fallbacks</p></article>
2443
+ <article class="card"><strong>${String(report.summary.degraded)}</strong><p>degraded</p></article>
2444
+ <article class="card"><strong>${String(report.summary.errors)}</strong><p>errors</p></article>
2445
+ </section>
2446
+ <section class="surfaces">
2447
+ ${report.surfaces.map((surface) => `<article class="surface">
2448
+ <header><strong>${escapeHtml7(surface.surface)}</strong> <span class="status ${surface.status}">${escapeHtml7(surface.status)}</span></header>
2449
+ <p>${String(surface.decisions)} decision(s), ${String(surface.fallbacks)} fallback(s), ${String(surface.degraded)} degraded decision(s), ${String(surface.errors)} error(s).</p>
2450
+ <p class="muted">Providers: ${escapeHtml7(surface.providers.join(", ") || "none")}</p>
2451
+ <p>${surface.reasons.map((reason) => `<code>${escapeHtml7(reason)}</code>`).join(" ")}</p>
2452
+ </article>`).join(`
2453
+ `)}
2454
+ </section>
2455
+ </main>
2456
+ </body>
2457
+ </html>`;
2458
+ var createVoiceProviderDecisionTraceRoutes = (options) => {
2459
+ const path = options.path ?? "/api/voice/provider-decisions";
2460
+ const htmlPath = options.htmlPath ?? "/voice/provider-decisions";
2461
+ const markdownPath = options.markdownPath ?? "/voice/provider-decisions.md";
2462
+ const headers = options.headers ?? {};
2463
+ const title = options.title ?? "Provider Decision Traces";
2464
+ const report = () => buildVoiceProviderDecisionTraceReport(options);
2465
+ const app = new Elysia3({ name: options.name ?? "voice-provider-decisions" }).get(path, async () => new Response(JSON.stringify(await report(), null, 2), {
2466
+ headers: {
2467
+ "content-type": "application/json; charset=utf-8",
2468
+ ...headers
2469
+ }
2470
+ }));
2471
+ if (htmlPath !== false) {
2472
+ app.get(htmlPath, async () => {
2473
+ const body = options.render ? await options.render(await report()) : renderVoiceProviderDecisionTraceHTML(await report(), title);
2474
+ return new Response(body, {
2475
+ headers: {
2476
+ "content-type": "text/html; charset=utf-8",
2477
+ ...headers
2478
+ }
2479
+ });
2480
+ });
2481
+ }
2482
+ if (markdownPath !== false) {
2483
+ app.get(markdownPath, async () => new Response(renderVoiceProviderDecisionTraceMarkdown(await report()), {
2484
+ headers: {
2485
+ "content-type": "text/markdown; charset=utf-8",
2486
+ ...headers
2487
+ }
2488
+ }));
2489
+ }
2490
+ return app;
2491
+ };
2492
+
2493
+ // src/trace.ts
2494
+ var createVoiceTraceEventId = (event) => [
2495
+ event.sessionId,
2496
+ event.turnId ?? "session",
2497
+ event.type,
2498
+ String(event.at ?? Date.now()),
2499
+ crypto.randomUUID()
2500
+ ].map(encodeURIComponent).join(":");
2501
+ var createVoiceTraceEvent = (event) => ({
2502
+ ...event,
2503
+ at: event.at,
2504
+ id: event.id ?? createVoiceTraceEventId({
2505
+ at: event.at,
2506
+ sessionId: event.sessionId,
2507
+ turnId: event.turnId,
2508
+ type: event.type
2509
+ })
2510
+ });
2511
+ var createVoiceTraceSinkDeliveryId = (events) => {
2512
+ const firstEvent = events[0];
2513
+ return [
2514
+ firstEvent?.sessionId ?? "trace",
2515
+ firstEvent?.traceId ?? "sink",
2516
+ String(firstEvent?.at ?? Date.now()),
2517
+ crypto.randomUUID()
2518
+ ].map(encodeURIComponent).join(":");
2519
+ };
2520
+ var createVoiceTraceSinkDeliveryRecord = (input) => {
2521
+ const createdAt = input.createdAt ?? Date.now();
2522
+ return {
2523
+ createdAt,
2524
+ deliveredAt: input.deliveredAt,
2525
+ deliveryAttempts: input.deliveryAttempts,
2526
+ deliveryError: input.deliveryError,
2527
+ deliveryStatus: input.deliveryStatus ?? "pending",
2528
+ events: input.events,
2529
+ id: input.id ?? createVoiceTraceSinkDeliveryId(input.events),
2530
+ sinkDeliveries: input.sinkDeliveries,
2531
+ updatedAt: input.updatedAt ?? createdAt
2532
+ };
2533
+ };
2534
+ var matchesTraceFilter = (event, filter) => {
2535
+ if (filter.sessionId !== undefined && event.sessionId !== filter.sessionId) {
2536
+ return false;
2537
+ }
2538
+ if (filter.turnId !== undefined && event.turnId !== filter.turnId) {
2539
+ return false;
2540
+ }
2541
+ if (filter.scenarioId !== undefined && event.scenarioId !== filter.scenarioId) {
2542
+ return false;
2543
+ }
2544
+ if (filter.traceId !== undefined && event.traceId !== filter.traceId) {
2545
+ return false;
2546
+ }
2547
+ if (filter.type !== undefined) {
2548
+ const types = Array.isArray(filter.type) ? filter.type : [filter.type];
2549
+ if (!types.includes(event.type)) {
2550
+ return false;
2551
+ }
2552
+ }
2553
+ return true;
2554
+ };
2555
+ var filterVoiceTraceEvents = (events, filter = {}) => {
2556
+ const sorted = events.filter((event) => matchesTraceFilter(event, filter)).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
2557
+ return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
2558
+ };
2559
+ var isPruneTimeMatch = (event, options) => {
2560
+ if (typeof options.before === "number" && event.at >= options.before) {
2561
+ return false;
2562
+ }
2563
+ if (typeof options.beforeOrAt === "number" && event.at > options.beforeOrAt) {
2564
+ return false;
2565
+ }
2566
+ return true;
2567
+ };
2568
+ var selectVoiceTraceEventsForPrune = (events, options = {}) => {
2569
+ let candidates = filterVoiceTraceEvents(events, options.filter).filter((event) => isPruneTimeMatch(event, options));
2570
+ if (typeof options.keepNewest === "number" && options.keepNewest >= 0) {
2571
+ 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));
2572
+ candidates = candidates.filter((event) => !newestIds.has(event.id));
2573
+ }
2574
+ return typeof options.limit === "number" && options.limit >= 0 ? candidates.slice(0, options.limit) : candidates;
2575
+ };
2576
+ var pruneVoiceTraceEvents = async (options) => {
2577
+ const events = await options.store.list(options.filter);
2578
+ const deleted = selectVoiceTraceEventsForPrune(events, options);
2579
+ if (!options.dryRun) {
2580
+ await Promise.all(deleted.map((event) => options.store.remove(event.id)));
2581
+ }
2582
+ return {
2583
+ deleted,
2584
+ deletedCount: deleted.length,
2585
+ dryRun: Boolean(options.dryRun),
2586
+ scannedCount: events.length
2587
+ };
2588
+ };
2589
+ var sleep = async (delayMs) => {
2590
+ if (delayMs <= 0) {
2591
+ return;
2592
+ }
2593
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
2594
+ };
2595
+ var toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
2596
+ var signVoiceTraceSinkBody = async (input) => {
2597
+ const encoder = new TextEncoder;
2598
+ const key = await crypto.subtle.importKey("raw", encoder.encode(input.secret), {
2599
+ hash: "SHA-256",
2600
+ name: "HMAC"
2601
+ }, false, ["sign"]);
2602
+ const payload = encoder.encode(`${input.timestamp}.${input.body}`);
2603
+ const signature = await crypto.subtle.sign("HMAC", key, payload);
2604
+ return `sha256=${toHex(new Uint8Array(signature))}`;
2605
+ };
2606
+ var createVoiceTraceSinkDeliveryError = (input) => {
2607
+ if (input.response) {
2608
+ const statusText = input.response.statusText?.trim();
2609
+ return `Attempt ${input.attempt} failed with trace sink response ${input.response.status}${statusText ? ` ${statusText}` : ""}.`;
2610
+ }
2611
+ if (input.error instanceof Error) {
2612
+ return `Attempt ${input.attempt} failed: ${input.error.message}`;
2613
+ }
2614
+ return `Attempt ${input.attempt} failed: ${String(input.error)}`;
2615
+ };
2616
+ var normalizeVoiceTraceS3KeyPrefix = (prefix) => prefix?.trim().replace(/^\/+|\/+$/g, "") ?? "voice/trace-deliveries";
2617
+ var createVoiceTraceS3ObjectKey = (prefix, events) => {
2618
+ const firstEvent = events[0];
2619
+ const safeSessionId = encodeURIComponent(firstEvent?.sessionId ?? "trace");
2620
+ const safeEventId = encodeURIComponent(firstEvent?.id ?? crypto.randomUUID());
2621
+ return `${prefix}/${safeSessionId}/${Date.now()}-${safeEventId}.json`;
2622
+ };
2623
+ var resolveVoiceS3DeliveredTo = (options, key) => {
2624
+ const bucket = options.bucket;
2625
+ return bucket ? `s3://${bucket}/${key}` : `s3://${key}`;
2626
+ };
2627
+ var aggregateVoiceTraceSinkDeliveryStatus = (deliveries) => {
2628
+ const statuses = Object.values(deliveries).map((delivery) => delivery.status);
2629
+ if (statuses.length === 0 || statuses.every((status) => status === "skipped")) {
2630
+ return "skipped";
2631
+ }
2632
+ if (statuses.some((status) => status === "failed")) {
2633
+ return "failed";
2634
+ }
2635
+ return "delivered";
2636
+ };
2637
+ var createVoiceTraceHTTPSink = (options) => ({
2638
+ deliver: async ({ events }) => {
2639
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2640
+ if (typeof fetchImpl !== "function") {
2641
+ return {
2642
+ attempts: 0,
2643
+ deliveredTo: options.url,
2644
+ error: "Trace sink delivery failed: fetch is not available in this runtime.",
2645
+ eventCount: events.length,
2646
+ status: "failed"
2647
+ };
2648
+ }
2649
+ const maxRetries = Math.max(0, options.retries ?? 0);
2650
+ const backoffMs = Math.max(0, options.backoffMs ?? 250);
2651
+ const timeoutMs = Math.max(0, options.timeoutMs ?? 1e4);
2652
+ const payload = options.body ? await options.body({ events }) : {
2653
+ eventCount: events.length,
2654
+ events,
2655
+ source: "absolutejs-voice"
2656
+ };
2657
+ const body = JSON.stringify(payload);
2658
+ let lastError = "Trace sink delivery failed.";
2659
+ for (let attempt = 1;attempt <= maxRetries + 1; attempt += 1) {
2660
+ let controller;
2661
+ let timeout;
2662
+ try {
2663
+ const headers = {
2664
+ "content-type": "application/json",
2665
+ ...options.headers
2666
+ };
2667
+ if (options.signingSecret) {
2668
+ const timestamp = String(Date.now());
2669
+ headers["x-absolutejs-timestamp"] = timestamp;
2670
+ headers["x-absolutejs-signature"] = await signVoiceTraceSinkBody({
2671
+ body,
2672
+ secret: options.signingSecret,
2673
+ timestamp
2674
+ });
2675
+ }
2676
+ controller = timeoutMs > 0 ? new AbortController : undefined;
2677
+ if (controller && timeoutMs > 0) {
2678
+ timeout = setTimeout(() => controller?.abort(), timeoutMs);
2679
+ }
2680
+ const response = await fetchImpl(options.url, {
2681
+ body,
2682
+ headers,
2683
+ method: options.method ?? "POST",
2684
+ signal: controller?.signal
2685
+ });
2686
+ if (response.ok) {
2687
+ let responseBody;
2688
+ try {
2689
+ responseBody = await response.clone().json();
2690
+ } catch {
2691
+ responseBody = undefined;
2692
+ }
2693
+ return {
2694
+ attempts: attempt,
2695
+ deliveredAt: Date.now(),
2696
+ deliveredTo: options.url,
2697
+ eventCount: events.length,
2698
+ responseBody,
2699
+ status: "delivered"
2700
+ };
2701
+ }
2702
+ lastError = createVoiceTraceSinkDeliveryError({
2703
+ attempt,
2704
+ response
2705
+ });
2706
+ } catch (error) {
2707
+ lastError = createVoiceTraceSinkDeliveryError({
2708
+ attempt,
2709
+ error
2710
+ });
2711
+ } finally {
2712
+ if (timeout) {
2713
+ clearTimeout(timeout);
2714
+ }
2715
+ }
2716
+ if (attempt <= maxRetries) {
2717
+ await sleep(backoffMs * attempt);
2718
+ }
2719
+ }
2720
+ return {
2721
+ attempts: maxRetries + 1,
2722
+ deliveredTo: options.url,
2723
+ error: lastError,
2724
+ eventCount: events.length,
2725
+ status: "failed"
2726
+ };
2727
+ },
2728
+ eventTypes: options.eventTypes,
2729
+ id: options.id,
2730
+ kind: options.kind ?? "http"
2731
+ });
2732
+ var createVoiceTraceS3Sink = (options) => {
2733
+ const client = options.client ?? new Bun.S3Client(options);
2734
+ const keyPrefix = normalizeVoiceTraceS3KeyPrefix(options.keyPrefix);
2735
+ return {
2736
+ deliver: async ({ events }) => {
2737
+ const key = createVoiceTraceS3ObjectKey(keyPrefix, events);
2738
+ const payload = options.body ? await options.body({ events, key }) : {
2739
+ eventCount: events.length,
2740
+ events,
2741
+ key,
2742
+ source: "absolutejs-voice"
2743
+ };
2744
+ try {
2745
+ const file = client.file(key, options);
2746
+ await file.write(JSON.stringify(payload), {
2747
+ type: options.contentType ?? "application/json"
2748
+ });
2749
+ return {
2750
+ attempts: 1,
2751
+ deliveredAt: Date.now(),
2752
+ deliveredTo: resolveVoiceS3DeliveredTo(options, key),
2753
+ eventCount: events.length,
2754
+ responseBody: { key },
2755
+ status: "delivered"
2756
+ };
2757
+ } catch (error) {
2758
+ return {
2759
+ attempts: 1,
2760
+ deliveredTo: resolveVoiceS3DeliveredTo(options, key),
2761
+ error: error instanceof Error ? error.message : String(error),
2762
+ eventCount: events.length,
2763
+ status: "failed"
2764
+ };
2765
+ }
2766
+ },
2767
+ eventTypes: options.eventTypes,
2768
+ id: options.id,
2769
+ kind: options.kind ?? "s3"
2770
+ };
2771
+ };
2772
+ var deliverVoiceTraceEventsToSinks = async (input) => {
2773
+ const events = input.redact ? redactVoiceTraceEvents(input.events, input.redact) : input.events;
2774
+ const sinkDeliveries = {};
2775
+ for (const sink of input.sinks) {
2776
+ const sinkEvents = sink.eventTypes?.length ? events.filter((event) => sink.eventTypes?.includes(event.type)) : events;
2777
+ if (sinkEvents.length === 0) {
2778
+ sinkDeliveries[sink.id] = {
2779
+ attempts: 0,
2780
+ eventCount: 0,
2781
+ status: "skipped"
2782
+ };
2783
+ continue;
2784
+ }
2785
+ try {
2786
+ sinkDeliveries[sink.id] = await sink.deliver({
2787
+ events: sinkEvents
2788
+ });
2789
+ } catch (error) {
2790
+ sinkDeliveries[sink.id] = {
2791
+ attempts: 1,
2792
+ error: error instanceof Error ? error.message : String(error),
2793
+ eventCount: sinkEvents.length,
2794
+ status: "failed"
2795
+ };
2796
+ }
2797
+ }
2798
+ return {
2799
+ deliveredAt: Date.now(),
2800
+ eventCount: events.length,
2801
+ sinkDeliveries,
2802
+ status: aggregateVoiceTraceSinkDeliveryStatus(sinkDeliveries)
2803
+ };
2804
+ };
2805
+ var createVoiceTraceSinkStore = (options) => {
2806
+ const deliver = async (event) => {
2807
+ const result = await deliverVoiceTraceEventsToSinks({
2808
+ events: [event],
2809
+ redact: options.redact,
2810
+ sinks: options.sinks
2811
+ });
2812
+ await options.onDelivery?.(result);
2813
+ };
2814
+ return {
2815
+ append: async (event) => {
2816
+ const stored = await options.store.append(event);
2817
+ if (options.deliveryQueue) {
2818
+ const delivery2 = createVoiceTraceSinkDeliveryRecord({
2819
+ events: [stored]
2820
+ });
2821
+ await options.deliveryQueue.set(delivery2.id, delivery2);
2822
+ return stored;
2823
+ }
2824
+ const delivery = deliver(stored);
2825
+ if (options.awaitDelivery) {
2826
+ await delivery;
2827
+ } else {
2828
+ delivery.catch((error) => {
2829
+ options.onError?.(error);
2830
+ });
2831
+ }
2832
+ return stored;
2833
+ },
2834
+ get: (id) => options.store.get(id),
2835
+ list: (filter) => options.store.list(filter),
2836
+ remove: (id) => options.store.remove(id)
2837
+ };
2838
+ };
2839
+ var normalizeVoiceProfileTraceTaggerProfile = (profile) => typeof profile === "string" ? { id: profile } : profile?.id ? profile : undefined;
2840
+ var createVoiceProfileTraceTagger = (options) => {
2841
+ const profiles = new Map((options.profiles ?? []).map((profile) => [profile.id, profile]));
2842
+ const defaultProfile = normalizeVoiceProfileTraceTaggerProfile(options.defaultProfile);
2843
+ const resolveProfile = async (event) => {
2844
+ const resolved = normalizeVoiceProfileTraceTaggerProfile(await options.resolveProfile?.(event));
2845
+ const profile = resolved ?? defaultProfile;
2846
+ return profile ? profiles.get(profile.id) ?? profile : undefined;
2847
+ };
2848
+ return {
2849
+ append: async (event) => {
2850
+ const profile = await resolveProfile(event);
2851
+ if (!profile) {
2852
+ return options.store.append(event);
2853
+ }
2854
+ const metadata = {
2855
+ ...event.metadata ?? {},
2856
+ benchmarkProfileId: profile.id,
2857
+ profileDescription: event.metadata?.profileDescription ?? profile.description,
2858
+ profileId: profile.id,
2859
+ profileLabel: event.metadata?.profileLabel ?? profile.label
2860
+ };
2861
+ const payload = event.payload && typeof event.payload === "object" ? {
2862
+ ...event.payload,
2863
+ benchmarkProfileId: event.payload.benchmarkProfileId ?? profile.id,
2864
+ profileDescription: event.payload.profileDescription ?? profile.description,
2865
+ profileId: event.payload.profileId ?? profile.id,
2866
+ profileLabel: event.payload.profileLabel ?? profile.label
2867
+ } : event.payload;
2868
+ return options.store.append({
2869
+ ...event,
2870
+ metadata,
2871
+ payload
2872
+ });
2873
+ },
2874
+ get: (id) => options.store.get(id),
2875
+ list: (filter) => options.store.list(filter),
2876
+ remove: (id) => options.store.remove(id)
2877
+ };
2878
+ };
2879
+ var createVoiceMemoryTraceSinkDeliveryStore = () => {
2880
+ const deliveries = new Map;
2881
+ return {
2882
+ get: async (id) => deliveries.get(id),
2883
+ list: async () => [...deliveries.values()].sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id)),
2884
+ remove: async (id) => {
2885
+ deliveries.delete(id);
2886
+ },
2887
+ set: async (id, delivery) => {
2888
+ deliveries.set(id, delivery);
2889
+ }
2890
+ };
2891
+ };
2892
+ var createVoiceMemoryTraceEventStore = () => {
2893
+ const events = new Map;
2894
+ const append = async (event) => {
2895
+ const stored = createVoiceTraceEvent(event);
2896
+ events.set(stored.id, stored);
2897
+ return stored;
2898
+ };
2899
+ const get = async (id) => events.get(id);
2900
+ const list = async (filter) => filterVoiceTraceEvents([...events.values()], filter);
2901
+ const remove = async (id) => {
2902
+ events.delete(id);
2903
+ };
2904
+ return { append, get, list, remove };
2905
+ };
2906
+ var exportVoiceTrace = async (input) => {
2907
+ const events = await input.store.list(input.filter);
2908
+ return {
2909
+ exportedAt: Date.now(),
2910
+ events: input.redact ? redactVoiceTraceEvents(events, input.redact) : events,
2911
+ filter: input.filter,
2912
+ redacted: Boolean(input.redact)
2913
+ };
2914
+ };
2915
+ var toNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
2916
+ var escapeHtml8 = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2917
+ var formatTraceValue = (value) => {
2918
+ if (value === undefined || value === null) {
2919
+ return "";
2920
+ }
2921
+ if (typeof value === "string") {
2922
+ return value;
2923
+ }
2924
+ if (typeof value === "number" || typeof value === "boolean") {
2925
+ return String(value);
2926
+ }
2927
+ try {
2928
+ return JSON.stringify(value);
2929
+ } catch {
2930
+ return String(value);
2931
+ }
2932
+ };
2933
+ var DEFAULT_REDACTION_KEYS = [
2934
+ "apiKey",
2935
+ "authorization",
2936
+ "creditCard",
2937
+ "email",
2938
+ "externalId",
2939
+ "password",
2940
+ "phone",
2941
+ "secret",
2942
+ "ssn",
2943
+ "token"
2944
+ ];
2945
+ var DEFAULT_REDACTION_TEXT_KEYS = [
2946
+ "assistantText",
2947
+ "content",
2948
+ "error",
2949
+ "reason",
2950
+ "summary",
2951
+ "text"
2952
+ ];
2953
+ var EMAIL_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
2954
+ var PHONE_PATTERN = /(?<!\d)(?:\+?1[\s.-]?)?(?:\(?\d{3}\)?[\s.-]?)\d{3}[\s.-]?\d{4}(?!\d)/g;
2955
+ var normalizeRedactionKey = (key) => key.trim().toLowerCase().replace(/[^a-z0-9]/g, "");
2956
+ var resolveVoiceTraceRedactionOptions = (options = {}) => ({
2957
+ keys: typeof options === "boolean" ? DEFAULT_REDACTION_KEYS : options.keys ?? DEFAULT_REDACTION_KEYS,
2958
+ redactEmails: typeof options === "boolean" ? true : options.redactEmails ?? true,
2959
+ redactPhoneNumbers: typeof options === "boolean" ? true : options.redactPhoneNumbers ?? true,
2960
+ redactText: typeof options === "boolean" ? true : options.redactText ?? true,
2961
+ replacement: typeof options === "boolean" ? "[redacted]" : options.replacement ?? "[redacted]",
2962
+ textKeys: typeof options === "boolean" ? DEFAULT_REDACTION_TEXT_KEYS : options.textKeys ?? DEFAULT_REDACTION_TEXT_KEYS
2963
+ });
2964
+ var resolveReplacement = (input) => typeof input.options.replacement === "function" ? input.options.replacement({
2965
+ key: input.key,
2966
+ path: input.path,
2967
+ value: input.value
2968
+ }) : input.options.replacement;
2969
+ var redactVoiceTraceText = (value, options = {}, input = {}) => {
2970
+ const resolved = resolveVoiceTraceRedactionOptions(options);
2971
+ let redacted = value;
2972
+ const replacement = resolveReplacement({
2973
+ key: input.key,
2974
+ options: resolved,
2975
+ path: input.path ?? [],
2976
+ value
2977
+ });
2978
+ if (resolved.redactEmails) {
2979
+ redacted = redacted.replace(EMAIL_PATTERN, replacement);
2980
+ }
2981
+ if (resolved.redactPhoneNumbers) {
2982
+ redacted = redacted.replace(PHONE_PATTERN, replacement);
2983
+ }
2984
+ return redacted;
2985
+ };
2986
+ var redactTraceValue = (value, options, path) => {
2987
+ const key = path.at(-1);
2988
+ const normalizedKey = key ? normalizeRedactionKey(key) : undefined;
2989
+ const sensitiveKeys = new Set(options.keys.map(normalizeRedactionKey));
2990
+ const textKeys = new Set(options.textKeys.map(normalizeRedactionKey));
2991
+ if (normalizedKey && sensitiveKeys.has(normalizedKey) && (value === null || ["boolean", "number", "string", "undefined"].includes(typeof value))) {
2992
+ return resolveReplacement({
2993
+ key,
2994
+ options,
2995
+ path,
2996
+ value: String(value ?? "")
2997
+ });
2998
+ }
2999
+ if (typeof value === "string") {
3000
+ const shouldRedactText = options.redactText && (!normalizedKey || textKeys.has(normalizedKey) || path.length === 0);
3001
+ return shouldRedactText ? redactVoiceTraceText(value, options, {
3002
+ key,
3003
+ path
3004
+ }) : value;
3005
+ }
3006
+ if (Array.isArray(value)) {
3007
+ return value.map((item, index) => redactTraceValue(item, options, [...path, String(index)]));
3008
+ }
3009
+ if (typeof value === "object" && value) {
3010
+ return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [
3011
+ entryKey,
3012
+ redactTraceValue(entryValue, options, [...path, entryKey])
3013
+ ]));
3014
+ }
3015
+ return value;
3016
+ };
3017
+ var redactVoiceTraceEvent = (event, options = {}) => {
3018
+ const resolved = resolveVoiceTraceRedactionOptions(options);
3019
+ return {
3020
+ ...event,
3021
+ metadata: redactTraceValue(event.metadata, resolved, ["metadata"]),
3022
+ payload: redactTraceValue(event.payload, resolved, ["payload"])
3023
+ };
3024
+ };
3025
+ var redactVoiceTraceEvents = (events, options = {}) => events.map((event) => redactVoiceTraceEvent(event, options));
3026
+ var summarizeVoiceTrace = (events) => {
3027
+ const sorted = filterVoiceTraceEvents(events);
3028
+ const firstEvent = sorted[0];
3029
+ const lastEvent = sorted.at(-1);
3030
+ const lifecycleEvents = sorted.filter((event) => event.type === "call.lifecycle");
3031
+ const startEvent = lifecycleEvents.find((event) => event.payload.type === "start");
3032
+ const endEvent = lifecycleEvents.toReversed().find((event) => event.payload.type === "end");
3033
+ const costEvents = sorted.filter((event) => event.type === "turn.cost");
3034
+ const toolEvents = sorted.filter((event) => event.type === "agent.tool");
3035
+ const startedAt = startEvent?.at ?? firstEvent?.at;
3036
+ const endedAt = endEvent?.at ?? lastEvent?.at;
3037
+ const failed = sorted.some((event) => event.type === "session.error") || endEvent?.payload.disposition === "failed";
3038
+ return {
3039
+ assistantReplyCount: sorted.filter((event) => event.type === "turn.assistant").length,
3040
+ callDurationMs: startedAt !== undefined && endedAt !== undefined ? Math.max(0, endedAt - startedAt) : undefined,
3041
+ cost: {
3042
+ estimatedRelativeCostUnits: costEvents.reduce((total, event) => total + toNumber(event.payload.estimatedRelativeCostUnits), 0),
3043
+ totalBillableAudioMs: costEvents.reduce((total, event) => total + toNumber(event.payload.totalBillableAudioMs), 0)
3044
+ },
3045
+ endedAt,
3046
+ errorCount: sorted.filter((event) => event.type === "session.error").length,
3047
+ eventCount: sorted.length,
3048
+ failed,
3049
+ handoffCount: sorted.filter((event) => event.type === "agent.handoff").length,
3050
+ modelCallCount: sorted.filter((event) => event.type === "agent.model").length,
3051
+ sessionId: firstEvent?.sessionId,
3052
+ startedAt,
3053
+ toolCallCount: toolEvents.length,
3054
+ toolErrorCount: toolEvents.filter((event) => event.payload.status === "error").length,
3055
+ traceId: firstEvent?.traceId,
3056
+ transcriptCount: sorted.filter((event) => event.type === "turn.transcript").length,
3057
+ turnCount: sorted.filter((event) => event.type === "turn.committed").length
3058
+ };
3059
+ };
3060
+ var evaluateVoiceTrace = (events, options = {}) => {
3061
+ const summary = summarizeVoiceTrace(events);
3062
+ const issues = [];
3063
+ const maxHandoffs = options.maxHandoffs ?? 3;
3064
+ const maxToolErrors = options.maxToolErrors ?? 0;
3065
+ const maxModelCallsPerTurn = options.maxModelCallsPerTurn ?? 6;
3066
+ const turnCountForRatio = Math.max(1, summary.turnCount);
3067
+ if (options.requireCompletedCall !== false && !summary.endedAt) {
3068
+ issues.push({
3069
+ code: "call-not-ended",
3070
+ message: "Trace does not include a call end lifecycle event.",
3071
+ severity: "warning"
3072
+ });
3073
+ }
3074
+ if (summary.failed) {
3075
+ issues.push({
3076
+ code: "session-error",
3077
+ message: "Trace contains a session error or failed call disposition.",
3078
+ severity: "error"
3079
+ });
3080
+ }
3081
+ if (options.requireTranscript !== false && summary.transcriptCount === 0) {
3082
+ issues.push({
3083
+ code: "missing-transcript",
3084
+ message: "Trace does not include any transcript events.",
3085
+ severity: "error"
3086
+ });
3087
+ }
3088
+ if (options.requireTurn !== false && summary.turnCount === 0) {
3089
+ issues.push({
3090
+ code: "missing-turn",
3091
+ message: "Trace does not include any committed turns.",
3092
+ severity: "error"
3093
+ });
3094
+ }
3095
+ if (options.requireAssistantReply !== false && summary.turnCount > 0 && summary.assistantReplyCount === 0) {
3096
+ issues.push({
3097
+ code: "missing-assistant-reply",
3098
+ message: "Trace has committed turns but no assistant replies.",
3099
+ severity: "warning"
3100
+ });
3101
+ }
3102
+ if (summary.toolErrorCount > maxToolErrors) {
3103
+ issues.push({
3104
+ code: "tool-errors",
3105
+ message: `Trace has ${summary.toolErrorCount} tool error(s), above the allowed ${maxToolErrors}.`,
3106
+ severity: "error"
3107
+ });
3108
+ }
3109
+ if (summary.handoffCount > maxHandoffs) {
3110
+ issues.push({
3111
+ code: "too-many-handoffs",
3112
+ message: `Trace has ${summary.handoffCount} handoff(s), above the allowed ${maxHandoffs}.`,
3113
+ severity: "warning"
3114
+ });
3115
+ }
3116
+ if (summary.modelCallCount / turnCountForRatio > maxModelCallsPerTurn) {
3117
+ issues.push({
3118
+ code: "too-many-model-calls",
3119
+ message: `Trace averages more than ${maxModelCallsPerTurn} model calls per committed turn.`,
3120
+ severity: "warning"
3121
+ });
3122
+ }
3123
+ return {
3124
+ issues,
3125
+ pass: !issues.some((issue) => issue.severity === "error"),
3126
+ summary
3127
+ };
3128
+ };
3129
+ var renderTraceEventMarkdown = (event, startedAt) => {
3130
+ const offset = startedAt === undefined ? `${event.at}` : `+${Math.max(0, event.at - startedAt)}ms`;
3131
+ const label = `- ${offset} [${event.type}]`;
3132
+ switch (event.type) {
3133
+ case "turn.transcript":
3134
+ return `${label} ${event.payload.isFinal ? "final" : "partial"} "${formatTraceValue(event.payload.text)}"`;
3135
+ case "turn.committed":
3136
+ return `${label} committed "${formatTraceValue(event.payload.text)}"`;
3137
+ case "turn.assistant":
3138
+ return event.payload.text ? `${label} assistant "${formatTraceValue(event.payload.text)}"` : `${label} ${formatTraceValue(event.payload.status)}`;
3139
+ case "agent.tool":
3140
+ return `${label} ${formatTraceValue(event.payload.toolName)} ${formatTraceValue(event.payload.status)}`;
3141
+ case "agent.context":
3142
+ return `${label} ${formatTraceValue(event.payload.fromAgentId)} -> ${formatTraceValue(event.payload.targetAgentId)} ${formatTraceValue(event.payload.status)}`;
3143
+ case "agent.handoff":
3144
+ return `${label} ${formatTraceValue(event.payload.fromAgentId)} -> ${formatTraceValue(event.payload.targetAgentId)}`;
3145
+ case "session.error":
3146
+ return `${label} ${formatTraceValue(event.payload.error)}`;
3147
+ case "call.lifecycle":
3148
+ return `${label} ${formatTraceValue(event.payload.type)} ${formatTraceValue(event.payload.disposition)}`.trim();
3149
+ default:
3150
+ return `${label} ${formatTraceValue(event.payload)}`;
3151
+ }
3152
+ };
3153
+ var renderVoiceTraceMarkdown = (events, options = {}) => {
3154
+ const sorted = filterVoiceTraceEvents(options.redact ? redactVoiceTraceEvents(events, options.redact) : events);
3155
+ const summary = summarizeVoiceTrace(sorted);
3156
+ const evaluation = evaluateVoiceTrace(sorted, options.evaluation);
3157
+ const lines = [
3158
+ `# ${options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim()}`,
3159
+ "",
3160
+ `Pass: ${evaluation.pass ? "yes" : "no"}`,
3161
+ `Session: ${summary.sessionId ?? "unknown"}`,
3162
+ `Events: ${summary.eventCount}`,
3163
+ `Turns: ${summary.turnCount}`,
3164
+ `Transcripts: ${summary.transcriptCount}`,
3165
+ `Assistant replies: ${summary.assistantReplyCount}`,
3166
+ `Model calls: ${summary.modelCallCount}`,
3167
+ `Tool calls: ${summary.toolCallCount}`,
3168
+ `Handoffs: ${summary.handoffCount}`,
3169
+ `Errors: ${summary.errorCount}`,
3170
+ `Estimated cost units: ${summary.cost.estimatedRelativeCostUnits}`,
3171
+ ""
3172
+ ];
3173
+ if (evaluation.issues.length > 0) {
3174
+ lines.push("## Issues", "");
3175
+ for (const issue of evaluation.issues) {
3176
+ lines.push(`- [${issue.severity}] ${issue.code}: ${issue.message}`);
3177
+ }
3178
+ lines.push("");
3179
+ }
3180
+ lines.push("## Timeline", "");
3181
+ for (const event of sorted) {
3182
+ lines.push(renderTraceEventMarkdown(event, summary.startedAt));
3183
+ }
3184
+ return lines.join(`
3185
+ `);
3186
+ };
3187
+ var renderVoiceTraceHTML = (events, options = {}) => {
3188
+ const markdown = renderVoiceTraceMarkdown(events, options);
3189
+ const renderEvents = options.redact ? redactVoiceTraceEvents(events, options.redact) : events;
3190
+ const summary = summarizeVoiceTrace(renderEvents);
3191
+ const evaluation = evaluateVoiceTrace(renderEvents, options.evaluation);
3192
+ const eventRows = filterVoiceTraceEvents(renderEvents).map((event) => {
3193
+ const offset = summary.startedAt === undefined ? event.at : Math.max(0, event.at - summary.startedAt);
3194
+ return [
3195
+ "<tr>",
3196
+ `<td>${escapeHtml8(String(offset))}</td>`,
3197
+ `<td>${escapeHtml8(event.type)}</td>`,
3198
+ `<td>${escapeHtml8(event.turnId ?? "")}</td>`,
3199
+ `<td><code>${escapeHtml8(JSON.stringify(event.payload))}</code></td>`,
3200
+ "</tr>"
3201
+ ].join("");
3202
+ }).join(`
3203
+ `);
3204
+ return [
3205
+ "<!doctype html>",
3206
+ '<html lang="en">',
3207
+ "<head>",
3208
+ '<meta charset="utf-8" />',
3209
+ '<meta name="viewport" content="width=device-width, initial-scale=1" />',
3210
+ `<title>${escapeHtml8(options.title ?? "Voice Trace")}</title>`,
3211
+ "<style>",
3212
+ "body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;line-height:1.45;background:#f8f7f2;color:#181713}",
3213
+ "main{max-width:1100px;margin:auto}",
3214
+ ".summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:.75rem;margin:1rem 0}",
3215
+ ".card{background:white;border:1px solid #ded9cc;border-radius:12px;padding:1rem}",
3216
+ ".pass{color:#126b3a}.fail{color:#9d2222}",
3217
+ "table{border-collapse:collapse;width:100%;background:white;border:1px solid #ded9cc}",
3218
+ "th,td{border-bottom:1px solid #eee8dc;padding:.65rem;text-align:left;vertical-align:top}",
3219
+ "code{white-space:pre-wrap;word-break:break-word}",
3220
+ "pre{background:#181713;color:#f8f7f2;padding:1rem;border-radius:12px;overflow:auto}",
3221
+ "</style>",
3222
+ "</head>",
3223
+ "<body><main>",
3224
+ `<h1>${escapeHtml8(options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim())}</h1>`,
3225
+ `<p class="${evaluation.pass ? "pass" : "fail"}">QA: ${evaluation.pass ? "pass" : "fail"}</p>`,
3226
+ '<section class="summary">',
3227
+ `<div class="card"><strong>Events</strong><br>${summary.eventCount}</div>`,
3228
+ `<div class="card"><strong>Turns</strong><br>${summary.turnCount}</div>`,
3229
+ `<div class="card"><strong>Transcripts</strong><br>${summary.transcriptCount}</div>`,
3230
+ `<div class="card"><strong>Tool errors</strong><br>${summary.toolErrorCount}</div>`,
3231
+ `<div class="card"><strong>Cost units</strong><br>${summary.cost.estimatedRelativeCostUnits}</div>`,
3232
+ "</section>",
3233
+ "<h2>Timeline</h2>",
3234
+ "<table><thead><tr><th>Offset ms</th><th>Type</th><th>Turn</th><th>Payload</th></tr></thead><tbody>",
3235
+ eventRows,
3236
+ "</tbody></table>",
3237
+ "<h2>Markdown Export</h2>",
3238
+ `<pre>${escapeHtml8(markdown)}</pre>`,
3239
+ "</main></body></html>"
3240
+ ].join(`
3241
+ `);
3242
+ };
3243
+ var buildVoiceTraceReplay = (events, options = {}) => ({
3244
+ evaluation: evaluateVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events, options.evaluation),
3245
+ html: renderVoiceTraceHTML(events, options),
3246
+ markdown: renderVoiceTraceMarkdown(events, options),
3247
+ summary: summarizeVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events)
3248
+ });
3249
+
3250
+ // src/proofTrends.ts
1498
3251
  var DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS = 24 * 60 * 60 * 1000;
1499
3252
  var DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS = [
1500
3253
  {
@@ -1827,6 +3580,9 @@ var buildVoiceRealCallProfileEvidenceFromTraceEvents = (events, options = {}) =>
1827
3580
  const providerP95Ms = maxNumber(providers.map((provider) => provider.p95Ms));
1828
3581
  const runtimeChannel = summarizeRuntimeChannelTraceEvidence(sessionEvents);
1829
3582
  const surfaces = readRealCallProfileTraceSurfaces(sessionEvents);
3583
+ if (providers.length === 0 && runtimeChannel === undefined && liveLatencies.length === 0 && surfaces.length === 0) {
3584
+ return;
3585
+ }
1830
3586
  return {
1831
3587
  generatedAt: new Date(Math.max(...sessionEvents.map((event) => event.at))).toISOString(),
1832
3588
  liveP95Ms: percentile(liveLatencies, 95),
@@ -2291,6 +4047,98 @@ var uniqueVoiceRealCallProfileRecoveryLoopActions = (actions) => {
2291
4047
  return true;
2292
4048
  });
2293
4049
  };
4050
+ var normalizeRealCallProfileRecoveryProvider = (role, provider) => {
4051
+ if (!provider) {
4052
+ return;
4053
+ }
4054
+ if (typeof provider === "string") {
4055
+ return {
4056
+ provider,
4057
+ selectedProvider: provider,
4058
+ status: "selected"
4059
+ };
4060
+ }
4061
+ return {
4062
+ ...provider,
4063
+ selectedProvider: provider.selectedProvider ?? provider.provider,
4064
+ status: provider.status ?? "selected"
4065
+ };
4066
+ };
4067
+ var profileRealCallRecoveryEvent = (event, profileId, metadata = {}) => ({
4068
+ ...event,
4069
+ metadata: {
4070
+ ...metadata,
4071
+ ...event.metadata ?? {},
4072
+ benchmarkProfileId: event.metadata?.benchmarkProfileId ?? profileId,
4073
+ profileId: event.metadata?.profileId ?? profileId
4074
+ },
4075
+ payload: {
4076
+ ...event.payload ?? {},
4077
+ benchmarkProfileId: event.payload.benchmarkProfileId ?? profileId,
4078
+ profileId: event.payload.profileId ?? profileId
4079
+ }
4080
+ });
4081
+ var appendVoiceRealCallProfileRecoveryEvidence = async (options) => {
4082
+ const at = options.at ?? Date.now();
4083
+ const scenarioId = options.scenarioId ?? "real-call-profile-recovery";
4084
+ const sessionId = options.sessionId ?? `real-call-profile-recovery-${options.profileId}-${new Date(at).toISOString()}`;
4085
+ const browser = options.browser ?? {};
4086
+ const live = options.live ?? {};
4087
+ const providerEvents = [
4088
+ ["llm", 320, "live-call"],
4089
+ ["stt", 82, "live-stt"],
4090
+ ["tts", 45, "live-tts"]
4091
+ ].flatMap(([role, defaultElapsedMs, defaultSurface], index) => {
4092
+ const provider = normalizeRealCallProfileRecoveryProvider(role, options.providers?.[role]);
4093
+ if (!provider) {
4094
+ return [];
4095
+ }
4096
+ return [
4097
+ profileRealCallRecoveryEvent(createVoiceProviderDecisionTraceEvent({
4098
+ at: at + index,
4099
+ elapsedMs: provider.elapsedMs ?? defaultElapsedMs,
4100
+ kind: role,
4101
+ provider: provider.provider,
4102
+ reason: provider.reason ?? `Real-call profile recovery selected ${provider.provider} for ${role.toUpperCase()} evidence.`,
4103
+ scenarioId,
4104
+ selectedProvider: provider.selectedProvider,
4105
+ sessionId,
4106
+ status: provider.status,
4107
+ surface: provider.surface ?? defaultSurface
4108
+ }), options.profileId, options.metadata)
4109
+ ];
4110
+ });
4111
+ const browserEvents = browser === false ? [] : [
4112
+ profileRealCallRecoveryEvent(createVoiceTraceEvent({
4113
+ at: at + 3,
4114
+ payload: {
4115
+ firstAudioLatencyMs: browser.firstAudioLatencyMs ?? 420,
4116
+ messageCount: browser.messageCount,
4117
+ openSockets: browser.openSockets,
4118
+ receivedBytes: browser.receivedBytes,
4119
+ sentBytes: browser.sentBytes,
4120
+ status: browser.status ?? "pass"
4121
+ },
4122
+ scenarioId,
4123
+ sessionId,
4124
+ type: "client.browser_media"
4125
+ }), options.profileId, options.metadata)
4126
+ ];
4127
+ const liveEvents = live === false ? [] : [
4128
+ profileRealCallRecoveryEvent(createVoiceTraceEvent({
4129
+ at: at + 4,
4130
+ payload: {
4131
+ latencyMs: live.latencyMs ?? 420,
4132
+ status: live.status ?? "pass"
4133
+ },
4134
+ scenarioId,
4135
+ sessionId,
4136
+ type: "client.live_latency"
4137
+ }), options.profileId, options.metadata)
4138
+ ];
4139
+ const events = await Promise.all([...providerEvents, ...browserEvents, ...liveEvents].map((event) => options.store.append(event)));
4140
+ return { events, sessionId };
4141
+ };
2294
4142
  var runVoiceRealCallProfileRecoveryLoop = async (options) => {
2295
4143
  const baseUrl = options.baseUrl.replace(/\/$/, "");
2296
4144
  const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
@@ -3119,7 +4967,7 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
3119
4967
  }
3120
4968
  };
3121
4969
  };
3122
- var escapeHtml5 = (value) => String(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4970
+ var escapeHtml9 = (value) => String(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3123
4971
  var escapeMarkdown = (value) => value.replaceAll("|", "\\|");
3124
4972
  var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provider Runtime Recommendations") => [
3125
4973
  `# ${title}`,
@@ -3152,11 +5000,11 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
3152
5000
  ].join(`
3153
5001
  `);
3154
5002
  var renderVoiceProofTrendRecommendationHTML = (report, title = "Voice Provider Runtime Recommendations") => {
3155
- 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("");
3156
- const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml5(issue)}</li>`).join("");
3157
- 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("");
3158
- 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("");
3159
- 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>`;
5003
+ 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("");
5004
+ const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml9(issue)}</li>`).join("");
5005
+ 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("");
5006
+ 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("");
5007
+ 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>`;
3160
5008
  };
3161
5009
  var renderVoiceRealCallProfileHistoryMarkdown = (report, title = "Voice Real-Call Profile History") => [
3162
5010
  `# ${title}`,
@@ -3190,18 +5038,18 @@ var renderVoiceRealCallProfileHistoryMarkdown = (report, title = "Voice Real-Cal
3190
5038
  ].join(`
3191
5039
  `);
3192
5040
  var renderVoiceRealCallProfileHistoryHTML = (report, title = "Voice Real-Call Profile History") => {
3193
- 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>';
3194
- 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>';
3195
- 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("");
3196
- const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml5(issue)}</li>`).join("");
3197
- 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>`;
5041
+ 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>';
5042
+ 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>';
5043
+ 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("");
5044
+ const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml9(issue)}</li>`).join("");
5045
+ 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>`;
3198
5046
  };
3199
5047
  var createVoiceProofTrendRecommendationRoutes = (options) => {
3200
5048
  const path = options.path ?? "/api/voice/proof-trend-recommendations";
3201
5049
  const htmlPath = options.htmlPath === undefined ? "/voice/proof-trend-recommendations" : options.htmlPath;
3202
5050
  const markdownPath = options.markdownPath === undefined ? "/voice/proof-trend-recommendations.md" : options.markdownPath;
3203
5051
  const title = options.title ?? "Voice Provider Runtime Recommendations";
3204
- const routes = new Elysia({
5052
+ const routes = new Elysia4({
3205
5053
  name: options.name ?? "absolutejs-voice-proof-trend-recommendations"
3206
5054
  });
3207
5055
  const loadReport = async () => {
@@ -3243,7 +5091,7 @@ var createVoiceRealCallProfileHistoryRoutes = (options = {}) => {
3243
5091
  const htmlPath = options.htmlPath === undefined ? "/voice/real-call-profile-history" : options.htmlPath;
3244
5092
  const markdownPath = options.markdownPath === undefined ? "/voice/real-call-profile-history.md" : options.markdownPath;
3245
5093
  const title = options.title ?? "Voice Real-Call Profile History";
3246
- const routes = new Elysia({
5094
+ const routes = new Elysia4({
3247
5095
  name: options.name ?? "absolutejs-voice-real-call-profile-history"
3248
5096
  });
3249
5097
  const loadReport = async () => {
@@ -3295,7 +5143,7 @@ var loadVoiceRealCallProfileHistoryRouteReport = async (options) => {
3295
5143
  };
3296
5144
  var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
3297
5145
  const path = options.path ?? "/api/voice/real-call-profile-history";
3298
- const routes = new Elysia({
5146
+ const routes = new Elysia4({
3299
5147
  name: options.name ?? "absolutejs-voice-real-call-profile-recovery-actions"
3300
5148
  });
3301
5149
  const actionPath = (actionId) => `${path}${realCallProfileActionPaths[actionId]}`;
@@ -3470,7 +5318,7 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
3470
5318
  };
3471
5319
  var createVoiceProofTrendRoutes = (options) => {
3472
5320
  const path = options.path ?? "/api/voice/proof-trends";
3473
- const routes = new Elysia({
5321
+ const routes = new Elysia4({
3474
5322
  name: options.name ?? "absolutejs-voice-proof-trends"
3475
5323
  });
3476
5324
  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}`;
@@ -3757,7 +5605,7 @@ var DEFAULT_LINKS3 = [
3757
5605
  { href: "/voice/real-call-profile-history", label: "Profile history" },
3758
5606
  { href: "/api/voice/real-call-profile-history", label: "JSON" }
3759
5607
  ];
3760
- var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5608
+ var escapeHtml11 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3761
5609
  var formatMs2 = (value) => typeof value === "number" && Number.isFinite(value) ? `${Math.round(value)}ms` : "n/a";
3762
5610
  var formatProviderRoutes = (profile) => Object.entries(profile.providerRoutes).map(([role, provider]) => `${role}: ${provider}`).join(", ") || "No complete route yet";
3763
5611
  var createProfileView = (profile) => ({
@@ -3788,25 +5636,25 @@ var createVoiceProfileComparisonViewModel = (snapshot, options = {}) => {
3788
5636
  };
3789
5637
  var renderVoiceProfileComparisonHTML = (snapshot, options = {}) => {
3790
5638
  const model = createVoiceProfileComparisonViewModel(snapshot, options);
3791
- const profiles = model.profiles.length ? `<div class="absolute-voice-profile-comparison__profiles">${model.profiles.map((profile) => `<article class="absolute-voice-profile-comparison__profile absolute-voice-profile-comparison__profile--${escapeHtml7(profile.status)}">
5639
+ const profiles = model.profiles.length ? `<div class="absolute-voice-profile-comparison__profiles">${model.profiles.map((profile) => `<article class="absolute-voice-profile-comparison__profile absolute-voice-profile-comparison__profile--${escapeHtml11(profile.status)}">
3792
5640
  <header>
3793
- <span>${escapeHtml7(profile.status)}</span>
3794
- <strong>${escapeHtml7(profile.label)}</strong>
5641
+ <span>${escapeHtml11(profile.status)}</span>
5642
+ <strong>${escapeHtml11(profile.label)}</strong>
3795
5643
  </header>
3796
- <p>${escapeHtml7(profile.providerRoutes)}</p>
3797
- <div>${profile.evidence.map((metric) => `<span><small>${escapeHtml7(metric.label)}</small><b>${escapeHtml7(metric.value)}</b></span>`).join("")}</div>
3798
- <em>${escapeHtml7(profile.nextMove)}</em>
3799
- </article>`).join("")}</div>` : `<p class="absolute-voice-profile-comparison__empty">${model.error ? escapeHtml7(model.error) : "Run real-call profile collection to populate profile comparisons."}</p>`;
3800
- const links = model.links.length ? `<p class="absolute-voice-profile-comparison__links">${model.links.map((link) => `<a href="${escapeHtml7(link.href)}">${escapeHtml7(link.label)}</a>`).join("")}</p>` : "";
3801
- return `<section class="absolute-voice-profile-comparison absolute-voice-profile-comparison--${escapeHtml7(model.status)}">
5644
+ <p>${escapeHtml11(profile.providerRoutes)}</p>
5645
+ <div>${profile.evidence.map((metric) => `<span><small>${escapeHtml11(metric.label)}</small><b>${escapeHtml11(metric.value)}</b></span>`).join("")}</div>
5646
+ <em>${escapeHtml11(profile.nextMove)}</em>
5647
+ </article>`).join("")}</div>` : `<p class="absolute-voice-profile-comparison__empty">${model.error ? escapeHtml11(model.error) : "Run real-call profile collection to populate profile comparisons."}</p>`;
5648
+ const links = model.links.length ? `<p class="absolute-voice-profile-comparison__links">${model.links.map((link) => `<a href="${escapeHtml11(link.href)}">${escapeHtml11(link.label)}</a>`).join("")}</p>` : "";
5649
+ return `<section class="absolute-voice-profile-comparison absolute-voice-profile-comparison--${escapeHtml11(model.status)}">
3802
5650
  <header class="absolute-voice-profile-comparison__header">
3803
- <span class="absolute-voice-profile-comparison__eyebrow">${escapeHtml7(model.title)}</span>
3804
- <strong class="absolute-voice-profile-comparison__label">${escapeHtml7(model.label)}</strong>
5651
+ <span class="absolute-voice-profile-comparison__eyebrow">${escapeHtml11(model.title)}</span>
5652
+ <strong class="absolute-voice-profile-comparison__label">${escapeHtml11(model.label)}</strong>
3805
5653
  </header>
3806
- <p class="absolute-voice-profile-comparison__description">${escapeHtml7(model.description)}</p>
5654
+ <p class="absolute-voice-profile-comparison__description">${escapeHtml11(model.description)}</p>
3807
5655
  ${profiles}
3808
5656
  ${links}
3809
- ${model.error ? `<p class="absolute-voice-profile-comparison__error">${escapeHtml7(model.error)}</p>` : ""}
5657
+ ${model.error ? `<p class="absolute-voice-profile-comparison__error">${escapeHtml11(model.error)}</p>` : ""}
3810
5658
  </section>`;
3811
5659
  };
3812
5660
  var getVoiceProfileComparisonCSS = () => `.absolute-voice-profile-comparison{border:1px solid #c7d2fe;border-radius:20px;background:#eef2ff;color:#111827;padding:18px;box-shadow:0 18px 40px rgba(79,70,229,.12);font-family:inherit}.absolute-voice-profile-comparison--warning,.absolute-voice-profile-comparison--error{border-color:#fbbf24;background:#fffbeb}.absolute-voice-profile-comparison__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-profile-comparison__eyebrow{color:#4338ca;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-profile-comparison__label{font-size:24px;line-height:1}.absolute-voice-profile-comparison__description,.absolute-voice-profile-comparison__empty{color:#4b5563}.absolute-voice-profile-comparison__profiles{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));margin-top:14px}.absolute-voice-profile-comparison__profile{background:#fff;border:1px solid #c7d2fe;border-radius:16px;padding:14px}.absolute-voice-profile-comparison__profile--warn{border-color:#fbbf24}.absolute-voice-profile-comparison__profile--fail{border-color:#f87171}.absolute-voice-profile-comparison__profile header{align-items:center;display:flex;gap:8px;justify-content:space-between}.absolute-voice-profile-comparison__profile header span{border:1px solid currentColor;border-radius:999px;color:#4338ca;font-size:11px;font-weight:900;padding:3px 7px;text-transform:uppercase}.absolute-voice-profile-comparison__profile p{color:#1f2937;font-weight:800;overflow-wrap:anywhere}.absolute-voice-profile-comparison__profile div{display:grid;gap:8px;grid-template-columns:repeat(3,minmax(0,1fr))}.absolute-voice-profile-comparison__profile small{color:#6b7280;display:block;font-size:11px}.absolute-voice-profile-comparison__profile b{display:block}.absolute-voice-profile-comparison__profile em{color:#4b5563;display:block;font-size:13px;margin-top:12px}.absolute-voice-profile-comparison__links{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0 0}.absolute-voice-profile-comparison__links a{border:1px solid #a5b4fc;border-radius:999px;color:#4338ca;font-weight:800;padding:6px 10px;text-decoration:none}.absolute-voice-profile-comparison__error{color:#9f1239;font-weight:700}`;
@@ -4029,27 +5877,27 @@ var createVoiceProfileSwitchRecommendationStore = (path = "/api/voice/profile-sw
4029
5877
  // src/client/profileSwitchRecommendationWidget.ts
4030
5878
  var DEFAULT_TITLE7 = "Profile Switch Recommendation";
4031
5879
  var DEFAULT_DESCRIPTION7 = "Compares the current session against measured profile evidence and recommends whether to switch stacks.";
4032
- var escapeHtml8 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5880
+ var escapeHtml12 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4033
5881
  var formatRoute = (routes) => routes ? Object.entries(routes).map(([role, provider]) => `${role}: ${provider}`).join(", ") : "No route";
4034
5882
  var renderVoiceProfileSwitchRecommendationHTML = (snapshot, options = {}) => {
4035
5883
  const recommendation = snapshot.recommendation;
4036
5884
  const status = snapshot.error ? "error" : recommendation ? recommendation.status : snapshot.isLoading ? "loading" : "empty";
4037
5885
  const label = snapshot.error ? "Unavailable" : recommendation ? recommendation.status === "switch" ? `Switch to ${recommendation.recommendedProfile?.label ?? recommendation.recommendedProfile?.profileId ?? "recommended profile"}` : recommendation.status === "stay" ? "Keep current profile" : "Needs evidence" : snapshot.isLoading ? "Checking" : "No recommendation";
4038
5886
  const body = recommendation ? `<div class="absolute-voice-profile-switch__body">
4039
- <p><strong>Current:</strong> ${escapeHtml8(recommendation.currentProfile?.label ?? recommendation.currentProfile?.profileId ?? "Unknown")}</p>
4040
- <p><strong>Recommended:</strong> ${escapeHtml8(recommendation.recommendedProfile?.label ?? recommendation.recommendedProfile?.profileId ?? "None")}</p>
4041
- <p><strong>Routes:</strong> ${escapeHtml8(formatRoute(recommendation.recommendedProfile?.providerRoutes))}</p>
4042
- <ul>${recommendation.reasons.map((reason) => `<li>${escapeHtml8(reason)}</li>`).join("")}</ul>
4043
- <em>${escapeHtml8(recommendation.nextMove)}</em>
4044
- </div>` : `<p class="absolute-voice-profile-switch__empty">${escapeHtml8(snapshot.error ?? "Run session traffic to populate a recommendation.")}</p>`;
4045
- return `<section class="absolute-voice-profile-switch absolute-voice-profile-switch--${escapeHtml8(status)}">
5887
+ <p><strong>Current:</strong> ${escapeHtml12(recommendation.currentProfile?.label ?? recommendation.currentProfile?.profileId ?? "Unknown")}</p>
5888
+ <p><strong>Recommended:</strong> ${escapeHtml12(recommendation.recommendedProfile?.label ?? recommendation.recommendedProfile?.profileId ?? "None")}</p>
5889
+ <p><strong>Routes:</strong> ${escapeHtml12(formatRoute(recommendation.recommendedProfile?.providerRoutes))}</p>
5890
+ <ul>${recommendation.reasons.map((reason) => `<li>${escapeHtml12(reason)}</li>`).join("")}</ul>
5891
+ <em>${escapeHtml12(recommendation.nextMove)}</em>
5892
+ </div>` : `<p class="absolute-voice-profile-switch__empty">${escapeHtml12(snapshot.error ?? "Run session traffic to populate a recommendation.")}</p>`;
5893
+ return `<section class="absolute-voice-profile-switch absolute-voice-profile-switch--${escapeHtml12(status)}">
4046
5894
  <header class="absolute-voice-profile-switch__header">
4047
- <span class="absolute-voice-profile-switch__eyebrow">${escapeHtml8(options.title ?? DEFAULT_TITLE7)}</span>
4048
- <strong class="absolute-voice-profile-switch__label">${escapeHtml8(label)}</strong>
5895
+ <span class="absolute-voice-profile-switch__eyebrow">${escapeHtml12(options.title ?? DEFAULT_TITLE7)}</span>
5896
+ <strong class="absolute-voice-profile-switch__label">${escapeHtml12(label)}</strong>
4049
5897
  </header>
4050
- <p class="absolute-voice-profile-switch__description">${escapeHtml8(options.description ?? DEFAULT_DESCRIPTION7)}</p>
5898
+ <p class="absolute-voice-profile-switch__description">${escapeHtml12(options.description ?? DEFAULT_DESCRIPTION7)}</p>
4051
5899
  ${body}
4052
- ${snapshot.error ? `<p class="absolute-voice-profile-switch__error">${escapeHtml8(snapshot.error)}</p>` : ""}
5900
+ ${snapshot.error ? `<p class="absolute-voice-profile-switch__error">${escapeHtml12(snapshot.error)}</p>` : ""}
4053
5901
  </section>`;
4054
5902
  };
4055
5903
  var getVoiceProfileSwitchRecommendationCSS = () => `.absolute-voice-profile-switch{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-profile-switch--switch{border-color:#fdba74}.absolute-voice-profile-switch--stay{border-color:#86efac;background:#f0fdf4}.absolute-voice-profile-switch--warn,.absolute-voice-profile-switch--error{border-color:#fca5a5;background:#fff1f2}.absolute-voice-profile-switch__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-profile-switch__eyebrow{color:#c2410c;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-profile-switch__label{font-size:24px;line-height:1}.absolute-voice-profile-switch__description,.absolute-voice-profile-switch__body em,.absolute-voice-profile-switch__empty{color:#57534e}.absolute-voice-profile-switch__body{background:#fff;border:1px solid #fed7aa;border-radius:16px;margin-top:14px;padding:14px}.absolute-voice-profile-switch__body p{margin:.35rem 0}.absolute-voice-profile-switch__body ul{margin:.75rem 0;padding-left:1.2rem}.absolute-voice-profile-switch__body em{display:block}.absolute-voice-profile-switch__error{color:#9f1239;font-weight:700}`;
@@ -4204,7 +6052,7 @@ var DEFAULT_LINKS4 = [
4204
6052
  { href: "/production-readiness", label: "Readiness page" },
4205
6053
  { href: "/voice/slo-readiness-thresholds", label: "Gate thresholds" }
4206
6054
  ];
4207
- var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6055
+ var escapeHtml13 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4208
6056
  var formatExplanationValue = (value, unit) => {
4209
6057
  if (value === undefined || value === null) {
4210
6058
  return "n/a";
@@ -4245,23 +6093,23 @@ var createVoiceReadinessFailuresViewModel = (snapshot, options = {}) => {
4245
6093
  };
4246
6094
  var renderVoiceReadinessFailuresHTML = (snapshot, options = {}) => {
4247
6095
  const model = createVoiceReadinessFailuresViewModel(snapshot, options);
4248
- 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--${escapeHtml9(failure.status)}">
4249
- <span>${escapeHtml9(failure.status.toUpperCase())}</span>
4250
- <strong>${escapeHtml9(failure.label)}</strong>
4251
- <p>Observed ${escapeHtml9(failure.observed)} against ${escapeHtml9(failure.thresholdLabel)} ${escapeHtml9(failure.threshold)}.</p>
4252
- <p>${escapeHtml9(failure.remediation)}</p>
4253
- <p class="absolute-voice-readiness-failures__links">${failure.evidenceHref ? `<a href="${escapeHtml9(failure.evidenceHref)}">Evidence</a>` : ""}${failure.sourceHref ? `<a href="${escapeHtml9(failure.sourceHref)}">Threshold source</a>` : ""}</p>
4254
- </article>`).join("")}</div>` : `<p class="absolute-voice-readiness-failures__empty">${model.error ? escapeHtml9(model.error) : "No calibrated readiness gate explanations are open."}</p>`;
4255
- const links = model.links.length ? `<p class="absolute-voice-readiness-failures__links">${model.links.map((link) => `<a href="${escapeHtml9(link.href)}">${escapeHtml9(link.label)}</a>`).join("")}</p>` : "";
4256
- return `<section class="absolute-voice-readiness-failures absolute-voice-readiness-failures--${escapeHtml9(model.status)}">
6096
+ 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--${escapeHtml13(failure.status)}">
6097
+ <span>${escapeHtml13(failure.status.toUpperCase())}</span>
6098
+ <strong>${escapeHtml13(failure.label)}</strong>
6099
+ <p>Observed ${escapeHtml13(failure.observed)} against ${escapeHtml13(failure.thresholdLabel)} ${escapeHtml13(failure.threshold)}.</p>
6100
+ <p>${escapeHtml13(failure.remediation)}</p>
6101
+ <p class="absolute-voice-readiness-failures__links">${failure.evidenceHref ? `<a href="${escapeHtml13(failure.evidenceHref)}">Evidence</a>` : ""}${failure.sourceHref ? `<a href="${escapeHtml13(failure.sourceHref)}">Threshold source</a>` : ""}</p>
6102
+ </article>`).join("")}</div>` : `<p class="absolute-voice-readiness-failures__empty">${model.error ? escapeHtml13(model.error) : "No calibrated readiness gate explanations are open."}</p>`;
6103
+ const links = model.links.length ? `<p class="absolute-voice-readiness-failures__links">${model.links.map((link) => `<a href="${escapeHtml13(link.href)}">${escapeHtml13(link.label)}</a>`).join("")}</p>` : "";
6104
+ return `<section class="absolute-voice-readiness-failures absolute-voice-readiness-failures--${escapeHtml13(model.status)}">
4257
6105
  <header class="absolute-voice-readiness-failures__header">
4258
- <span class="absolute-voice-readiness-failures__eyebrow">${escapeHtml9(model.title)}</span>
4259
- <strong class="absolute-voice-readiness-failures__label">${escapeHtml9(model.label)}</strong>
6106
+ <span class="absolute-voice-readiness-failures__eyebrow">${escapeHtml13(model.title)}</span>
6107
+ <strong class="absolute-voice-readiness-failures__label">${escapeHtml13(model.label)}</strong>
4260
6108
  </header>
4261
- <p class="absolute-voice-readiness-failures__description">${escapeHtml9(model.description)}</p>
6109
+ <p class="absolute-voice-readiness-failures__description">${escapeHtml13(model.description)}</p>
4262
6110
  ${failures}
4263
6111
  ${links}
4264
- ${model.error ? `<p class="absolute-voice-readiness-failures__error">${escapeHtml9(model.error)}</p>` : ""}
6112
+ ${model.error ? `<p class="absolute-voice-readiness-failures__error">${escapeHtml13(model.error)}</p>` : ""}
4265
6113
  </section>`;
4266
6114
  };
4267
6115
  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}`;
@@ -4490,7 +6338,7 @@ var createVoiceProviderSimulationControlsStore = (options) => {
4490
6338
  };
4491
6339
 
4492
6340
  // src/client/providerSimulationControlsWidget.ts
4493
- var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6341
+ var escapeHtml14 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4494
6342
  var formatKind = (kind) => (kind ?? "stt").toUpperCase();
4495
6343
  var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
4496
6344
  const configuredProviders = options.providers.filter((provider) => provider.configured !== false);
@@ -4510,18 +6358,18 @@ var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
4510
6358
  };
4511
6359
  var renderVoiceProviderSimulationControlsHTML = (snapshot, options) => {
4512
6360
  const model = createVoiceProviderSimulationControlsViewModel(snapshot, options);
4513
- const failureButtons = model.failureProviders.map((provider) => `<button type="button" data-voice-provider-fail="${escapeHtml10(provider.provider)}"${!model.canSimulateFailure || snapshot.isRunning ? " disabled" : ""}>Simulate ${escapeHtml10(provider.provider)} ${escapeHtml10(formatKind(options.kind))} failure</button>`).join("");
4514
- const recoveryButtons = model.providers.map((provider) => `<button type="button" data-voice-provider-recover="${escapeHtml10(provider.provider)}"${snapshot.isRunning ? " disabled" : ""}>Mark ${escapeHtml10(provider.provider)} recovered</button>`).join("");
6361
+ const failureButtons = model.failureProviders.map((provider) => `<button type="button" data-voice-provider-fail="${escapeHtml14(provider.provider)}"${!model.canSimulateFailure || snapshot.isRunning ? " disabled" : ""}>Simulate ${escapeHtml14(provider.provider)} ${escapeHtml14(formatKind(options.kind))} failure</button>`).join("");
6362
+ const recoveryButtons = model.providers.map((provider) => `<button type="button" data-voice-provider-recover="${escapeHtml14(provider.provider)}"${snapshot.isRunning ? " disabled" : ""}>Mark ${escapeHtml14(provider.provider)} recovered</button>`).join("");
4515
6363
  return `<section class="absolute-voice-provider-simulation absolute-voice-provider-simulation--${snapshot.error ? "error" : snapshot.isRunning ? "running" : "ready"}">
4516
6364
  <header class="absolute-voice-provider-simulation__header">
4517
- <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml10(model.title)}</span>
4518
- <strong class="absolute-voice-provider-simulation__label">${escapeHtml10(model.label)}</strong>
6365
+ <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml14(model.title)}</span>
6366
+ <strong class="absolute-voice-provider-simulation__label">${escapeHtml14(model.label)}</strong>
4519
6367
  </header>
4520
- <p class="absolute-voice-provider-simulation__description">${escapeHtml10(model.description)}</p>
4521
- ${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml10(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
6368
+ <p class="absolute-voice-provider-simulation__description">${escapeHtml14(model.description)}</p>
6369
+ ${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml14(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
4522
6370
  <div class="absolute-voice-provider-simulation__actions">${failureButtons}${recoveryButtons}</div>
4523
- ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml10(snapshot.error)}</p>` : ""}
4524
- ${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml10(model.resultText)}</pre>` : ""}
6371
+ ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml14(snapshot.error)}</p>` : ""}
6372
+ ${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml14(model.resultText)}</pre>` : ""}
4525
6373
  </section>`;
4526
6374
  };
4527
6375
  var bindVoiceProviderSimulationControls = (element, store) => {
@@ -4781,7 +6629,7 @@ var useVoiceProviderCapabilities = (path = "/api/provider-capabilities", options
4781
6629
  // src/client/providerCapabilitiesWidget.ts
4782
6630
  var DEFAULT_TITLE9 = "Provider Capabilities";
4783
6631
  var DEFAULT_DESCRIPTION9 = "Configured, selected, and healthy voice providers for this deployment.";
4784
- var escapeHtml11 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6632
+ var escapeHtml15 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4785
6633
  var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
4786
6634
  var formatKind2 = (kind) => kind.toUpperCase();
4787
6635
  var formatStatus2 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
@@ -4836,25 +6684,25 @@ var createVoiceProviderCapabilitiesViewModel = (snapshot, options = {}) => {
4836
6684
  };
4837
6685
  var renderVoiceProviderCapabilitiesHTML = (snapshot, options = {}) => {
4838
6686
  const model = createVoiceProviderCapabilitiesViewModel(snapshot, options);
4839
- 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--${escapeHtml11(capability.status)}">
6687
+ 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--${escapeHtml15(capability.status)}">
4840
6688
  <header>
4841
- <strong>${escapeHtml11(capability.label)}</strong>
4842
- <span>${escapeHtml11(formatStatus2(capability.status))}</span>
6689
+ <strong>${escapeHtml15(capability.label)}</strong>
6690
+ <span>${escapeHtml15(formatStatus2(capability.status))}</span>
4843
6691
  </header>
4844
- <p>${escapeHtml11(capability.detail)}</p>
6692
+ <p>${escapeHtml15(capability.detail)}</p>
4845
6693
  <dl>${capability.rows.map((row) => `<div>
4846
- <dt>${escapeHtml11(row.label)}</dt>
4847
- <dd>${escapeHtml11(row.value)}</dd>
6694
+ <dt>${escapeHtml15(row.label)}</dt>
6695
+ <dd>${escapeHtml15(row.value)}</dd>
4848
6696
  </div>`).join("")}</dl>
4849
6697
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-capabilities__empty">Configure provider capabilities to see deployment coverage.</p>';
4850
- return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml11(model.status)}">
6698
+ return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml15(model.status)}">
4851
6699
  <header class="absolute-voice-provider-capabilities__header">
4852
- <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml11(model.title)}</span>
4853
- <strong class="absolute-voice-provider-capabilities__label">${escapeHtml11(model.label)}</strong>
6700
+ <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml15(model.title)}</span>
6701
+ <strong class="absolute-voice-provider-capabilities__label">${escapeHtml15(model.label)}</strong>
4854
6702
  </header>
4855
- <p class="absolute-voice-provider-capabilities__description">${escapeHtml11(model.description)}</p>
6703
+ <p class="absolute-voice-provider-capabilities__description">${escapeHtml15(model.description)}</p>
4856
6704
  ${capabilities}
4857
- ${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml11(model.error)}</p>` : ""}
6705
+ ${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml15(model.error)}</p>` : ""}
4858
6706
  </section>`;
4859
6707
  };
4860
6708
  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}`;
@@ -5072,7 +6920,7 @@ var useVoiceProviderContracts = (path = "/api/provider-contracts", options = {})
5072
6920
  // src/client/providerContractsWidget.ts
5073
6921
  var DEFAULT_TITLE10 = "Provider Contracts";
5074
6922
  var DEFAULT_DESCRIPTION10 = "Production contract coverage for provider env, latency, fallback, streaming, and capabilities.";
5075
- var escapeHtml12 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6923
+ var escapeHtml16 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5076
6924
  var formatProvider2 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
5077
6925
  var formatStatus3 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
5078
6926
  var contractDetail = (row) => {
@@ -5116,26 +6964,26 @@ var createVoiceProviderContractsViewModel = (snapshot, options = {}) => {
5116
6964
  };
5117
6965
  var renderVoiceProviderContractsHTML = (snapshot, options = {}) => {
5118
6966
  const model = createVoiceProviderContractsViewModel(snapshot, options);
5119
- 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--${escapeHtml12(row.status)}">
6967
+ 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--${escapeHtml16(row.status)}">
5120
6968
  <header>
5121
- <strong>${escapeHtml12(row.label)}</strong>
5122
- <span>${escapeHtml12(formatStatus3(row.status))}</span>
6969
+ <strong>${escapeHtml16(row.label)}</strong>
6970
+ <span>${escapeHtml16(formatStatus3(row.status))}</span>
5123
6971
  </header>
5124
- <p>${escapeHtml12(row.detail)}</p>
5125
- ${row.remediations.length ? `<ul class="absolute-voice-provider-contracts__remediations">${row.remediations.map((remediation) => `<li>${remediation.href ? `<a href="${escapeHtml12(remediation.href)}">${escapeHtml12(remediation.label)}</a>` : `<strong>${escapeHtml12(remediation.label)}</strong>`}<span>${escapeHtml12(remediation.detail)}</span></li>`).join("")}</ul>` : ""}
6972
+ <p>${escapeHtml16(row.detail)}</p>
6973
+ ${row.remediations.length ? `<ul class="absolute-voice-provider-contracts__remediations">${row.remediations.map((remediation) => `<li>${remediation.href ? `<a href="${escapeHtml16(remediation.href)}">${escapeHtml16(remediation.label)}</a>` : `<strong>${escapeHtml16(remediation.label)}</strong>`}<span>${escapeHtml16(remediation.detail)}</span></li>`).join("")}</ul>` : ""}
5126
6974
  <dl>${row.rows.map((item) => `<div>
5127
- <dt>${escapeHtml12(item.label)}</dt>
5128
- <dd>${escapeHtml12(item.value)}</dd>
6975
+ <dt>${escapeHtml16(item.label)}</dt>
6976
+ <dd>${escapeHtml16(item.value)}</dd>
5129
6977
  </div>`).join("")}</dl>
5130
6978
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-contracts__empty">Configure provider contracts to see production coverage.</p>';
5131
- return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${escapeHtml12(model.status)}">
6979
+ return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${escapeHtml16(model.status)}">
5132
6980
  <header class="absolute-voice-provider-contracts__header">
5133
- <span class="absolute-voice-provider-contracts__eyebrow">${escapeHtml12(model.title)}</span>
5134
- <strong class="absolute-voice-provider-contracts__label">${escapeHtml12(model.label)}</strong>
6981
+ <span class="absolute-voice-provider-contracts__eyebrow">${escapeHtml16(model.title)}</span>
6982
+ <strong class="absolute-voice-provider-contracts__label">${escapeHtml16(model.label)}</strong>
5135
6983
  </header>
5136
- <p class="absolute-voice-provider-contracts__description">${escapeHtml12(model.description)}</p>
6984
+ <p class="absolute-voice-provider-contracts__description">${escapeHtml16(model.description)}</p>
5137
6985
  ${rows}
5138
- ${model.error ? `<p class="absolute-voice-provider-contracts__error">${escapeHtml12(model.error)}</p>` : ""}
6986
+ ${model.error ? `<p class="absolute-voice-provider-contracts__error">${escapeHtml16(model.error)}</p>` : ""}
5139
6987
  </section>`;
5140
6988
  };
5141
6989
  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}`;
@@ -5374,7 +7222,7 @@ var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
5374
7222
  // src/client/providerStatusWidget.ts
5375
7223
  var DEFAULT_TITLE11 = "Voice Providers";
5376
7224
  var DEFAULT_DESCRIPTION11 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
5377
- var escapeHtml13 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
7225
+ var escapeHtml17 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5378
7226
  var formatProvider3 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
5379
7227
  var formatStatus4 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
5380
7228
  var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
@@ -5430,25 +7278,25 @@ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
5430
7278
  };
5431
7279
  var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
5432
7280
  const model = createVoiceProviderStatusViewModel(snapshot, options);
5433
- 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--${escapeHtml13(provider.status)}">
7281
+ 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--${escapeHtml17(provider.status)}">
5434
7282
  <header>
5435
- <strong>${escapeHtml13(provider.label)}</strong>
5436
- <span>${escapeHtml13(formatStatus4(provider.status))}</span>
7283
+ <strong>${escapeHtml17(provider.label)}</strong>
7284
+ <span>${escapeHtml17(formatStatus4(provider.status))}</span>
5437
7285
  </header>
5438
- <p>${escapeHtml13(provider.detail)}</p>
7286
+ <p>${escapeHtml17(provider.detail)}</p>
5439
7287
  <dl>${provider.rows.map((row) => `<div>
5440
- <dt>${escapeHtml13(row.label)}</dt>
5441
- <dd>${escapeHtml13(row.value)}</dd>
7288
+ <dt>${escapeHtml17(row.label)}</dt>
7289
+ <dd>${escapeHtml17(row.value)}</dd>
5442
7290
  </div>`).join("")}</dl>
5443
7291
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
5444
- return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml13(model.status)}">
7292
+ return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml17(model.status)}">
5445
7293
  <header class="absolute-voice-provider-status__header">
5446
- <span class="absolute-voice-provider-status__eyebrow">${escapeHtml13(model.title)}</span>
5447
- <strong class="absolute-voice-provider-status__label">${escapeHtml13(model.label)}</strong>
7294
+ <span class="absolute-voice-provider-status__eyebrow">${escapeHtml17(model.title)}</span>
7295
+ <strong class="absolute-voice-provider-status__label">${escapeHtml17(model.label)}</strong>
5448
7296
  </header>
5449
- <p class="absolute-voice-provider-status__description">${escapeHtml13(model.description)}</p>
7297
+ <p class="absolute-voice-provider-status__description">${escapeHtml17(model.description)}</p>
5450
7298
  ${providers}
5451
- ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml13(model.error)}</p>` : ""}
7299
+ ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml17(model.error)}</p>` : ""}
5452
7300
  </section>`;
5453
7301
  };
5454
7302
  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}`;
@@ -5671,7 +7519,7 @@ var useVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
5671
7519
  // src/client/routingStatusWidget.ts
5672
7520
  var DEFAULT_TITLE12 = "Voice Routing";
5673
7521
  var DEFAULT_DESCRIPTION12 = "Latest provider routing decision from the self-hosted trace store.";
5674
- var escapeHtml14 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
7522
+ var escapeHtml18 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5675
7523
  var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
5676
7524
  var formatProviderRoutes2 = (routes) => routes && typeof routes === "object" ? Object.entries(routes).map(([role, provider]) => `${role}: ${formatValue(provider)}`).join(", ") || "None" : "None";
5677
7525
  var getProviderRoute = (routes, role) => routes && typeof routes === "object" ? formatValue(routes[role], "Not configured") : "Not configured";
@@ -5753,22 +7601,22 @@ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
5753
7601
  var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
5754
7602
  const model = createVoiceRoutingStatusViewModel(snapshot, options);
5755
7603
  const activeStack = model.activeStack.length ? `<div class="absolute-voice-routing-status__stack" aria-label="Active voice stack">${model.activeStack.map((item) => `<div>
5756
- <span>${escapeHtml14(item.label)}</span>
5757
- <strong>${escapeHtml14(item.value)}</strong>
7604
+ <span>${escapeHtml18(item.label)}</span>
7605
+ <strong>${escapeHtml18(item.value)}</strong>
5758
7606
  </div>`).join("")}</div>` : "";
5759
7607
  const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
5760
- <span>${escapeHtml14(row.label)}</span>
5761
- <strong>${escapeHtml14(row.value)}</strong>
7608
+ <span>${escapeHtml18(row.label)}</span>
7609
+ <strong>${escapeHtml18(row.value)}</strong>
5762
7610
  </div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
5763
- return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml14(model.status)}">
7611
+ return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml18(model.status)}">
5764
7612
  <header class="absolute-voice-routing-status__header">
5765
- <span class="absolute-voice-routing-status__eyebrow">${escapeHtml14(model.title)}</span>
5766
- <strong class="absolute-voice-routing-status__label">${escapeHtml14(model.label)}</strong>
7613
+ <span class="absolute-voice-routing-status__eyebrow">${escapeHtml18(model.title)}</span>
7614
+ <strong class="absolute-voice-routing-status__label">${escapeHtml18(model.label)}</strong>
5767
7615
  </header>
5768
- <p class="absolute-voice-routing-status__description">${escapeHtml14(model.description)}</p>
7616
+ <p class="absolute-voice-routing-status__description">${escapeHtml18(model.description)}</p>
5769
7617
  ${activeStack}
5770
7618
  ${rows}
5771
- ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml14(model.error)}</p>` : ""}
7619
+ ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml18(model.error)}</p>` : ""}
5772
7620
  </section>`;
5773
7621
  };
5774
7622
  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}}`;
@@ -5948,7 +7796,7 @@ var createVoiceTraceTimelineStore = (path = "/api/voice-traces", options = {}) =
5948
7796
  // src/client/traceTimelineWidget.ts
5949
7797
  var DEFAULT_TITLE13 = "Voice Traces";
5950
7798
  var DEFAULT_DESCRIPTION13 = "Latest call timelines with provider latency, fallbacks, handoffs, and errors from your self-hosted trace store.";
5951
- var escapeHtml15 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
7799
+ var escapeHtml19 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5952
7800
  var formatMs3 = (value) => typeof value === "number" ? `${value}ms` : "n/a";
5953
7801
  var formatProviders = (session) => session.providers.length ? session.providers.map((provider) => provider.provider).join(", ") : "No providers";
5954
7802
  var createVoiceTraceTimelineViewModel = (snapshot, options = {}) => {
@@ -5978,27 +7826,27 @@ var renderVoiceTraceTimelineWidgetHTML = (snapshot, options = {}) => {
5978
7826
  const model = createVoiceTraceTimelineViewModel(snapshot, options);
5979
7827
  const sessions = model.sessions.length ? `<div class="absolute-voice-trace-timeline__sessions">${model.sessions.map((session) => {
5980
7828
  const supportLinks = [
5981
- `<a href="${escapeHtml15(session.detailHref)}">Open timeline</a>`,
5982
- session.operationsRecordHref ? `<a href="${escapeHtml15(session.operationsRecordHref)}">Open operations record</a>` : undefined,
5983
- session.incidentBundleHref ? `<a href="${escapeHtml15(session.incidentBundleHref)}">Export incident bundle</a>` : undefined
7829
+ `<a href="${escapeHtml19(session.detailHref)}">Open timeline</a>`,
7830
+ session.operationsRecordHref ? `<a href="${escapeHtml19(session.operationsRecordHref)}">Open operations record</a>` : undefined,
7831
+ session.incidentBundleHref ? `<a href="${escapeHtml19(session.incidentBundleHref)}">Export incident bundle</a>` : undefined
5984
7832
  ].filter(Boolean).join("");
5985
- return `<article class="absolute-voice-trace-timeline__session absolute-voice-trace-timeline__session--${escapeHtml15(session.status)}">
7833
+ return `<article class="absolute-voice-trace-timeline__session absolute-voice-trace-timeline__session--${escapeHtml19(session.status)}">
5986
7834
  <header>
5987
- <strong>${escapeHtml15(session.sessionId)}</strong>
5988
- <span>${escapeHtml15(session.status)}</span>
7835
+ <strong>${escapeHtml19(session.sessionId)}</strong>
7836
+ <span>${escapeHtml19(session.status)}</span>
5989
7837
  </header>
5990
- <p>${escapeHtml15(session.label)} \xB7 ${escapeHtml15(session.durationLabel)} \xB7 ${escapeHtml15(session.providerLabel)}</p>
7838
+ <p>${escapeHtml19(session.label)} \xB7 ${escapeHtml19(session.durationLabel)} \xB7 ${escapeHtml19(session.providerLabel)}</p>
5991
7839
  <p class="absolute-voice-trace-timeline__actions">${supportLinks}</p>
5992
7840
  </article>`;
5993
7841
  }).join("")}</div>` : '<p class="absolute-voice-trace-timeline__empty">Run a voice session to see call timelines.</p>';
5994
- return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml15(model.status)}">
7842
+ return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml19(model.status)}">
5995
7843
  <header class="absolute-voice-trace-timeline__header">
5996
- <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml15(model.title)}</span>
5997
- <strong class="absolute-voice-trace-timeline__label">${escapeHtml15(model.label)}</strong>
7844
+ <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml19(model.title)}</span>
7845
+ <strong class="absolute-voice-trace-timeline__label">${escapeHtml19(model.label)}</strong>
5998
7846
  </header>
5999
- <p class="absolute-voice-trace-timeline__description">${escapeHtml15(model.description)}</p>
7847
+ <p class="absolute-voice-trace-timeline__description">${escapeHtml19(model.description)}</p>
6000
7848
  ${sessions}
6001
- ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml15(model.error)}</p>` : ""}
7849
+ ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml19(model.error)}</p>` : ""}
6002
7850
  </section>`;
6003
7851
  };
6004
7852
  var getVoiceTraceTimelineCSS = () => `.absolute-voice-trace-timeline{border:1px solid #bad7d3;border-radius:20px;background:#f3fffb;color:#09201c;padding:18px;box-shadow:0 18px 40px rgba(9,32,28,.12);font-family:inherit}.absolute-voice-trace-timeline--error,.absolute-voice-trace-timeline--failed{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-trace-timeline--warning{border-color:#fbbf24;background:#fffaf0}.absolute-voice-trace-timeline__header,.absolute-voice-trace-timeline__session header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-trace-timeline__eyebrow{color:#17665b;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-trace-timeline__label{font-size:24px;line-height:1}.absolute-voice-trace-timeline__description,.absolute-voice-trace-timeline__session p,.absolute-voice-trace-timeline__empty{color:#35544f}.absolute-voice-trace-timeline__sessions{display:grid;gap:12px;margin-top:14px}.absolute-voice-trace-timeline__session{background:#fff;border:1px solid #cfe7e2;border-radius:16px;padding:14px}.absolute-voice-trace-timeline__session--failed{border-color:#f2a7a7}.absolute-voice-trace-timeline__session--warning{border-color:#fbbf24}.absolute-voice-trace-timeline__session p{margin:10px 0}.absolute-voice-trace-timeline__actions{display:flex;flex-wrap:wrap;gap:10px}.absolute-voice-trace-timeline__session a{color:#0f766e;font-weight:800}.absolute-voice-trace-timeline__empty{margin:14px 0 0}.absolute-voice-trace-timeline__error{color:#9f1239;font-weight:700}`;
@@ -6157,8 +8005,8 @@ var VoiceTraceTimeline = ({
6157
8005
  import { useEffect as useEffect15, useRef as useRef15, useSyncExternalStore as useSyncExternalStore15 } from "react";
6158
8006
 
6159
8007
  // src/client/agentSquadStatus.ts
6160
- var getString = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
6161
- var getPayloadString = (event, key) => getString(event.payload?.[key]);
8008
+ var getString4 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
8009
+ var getPayloadString = (event, key) => getString4(event.payload?.[key]);
6162
8010
  var eventStatus = (event) => {
6163
8011
  const status = getPayloadString(event, "status");
6164
8012
  if (status === "blocked")
@@ -6250,7 +8098,7 @@ var useVoiceAgentSquadStatus = (path = "/api/voice-traces", options = {}) => {
6250
8098
  // src/client/agentSquadStatusWidget.ts
6251
8099
  var DEFAULT_TITLE14 = "Voice Agent Squad";
6252
8100
  var DEFAULT_DESCRIPTION14 = "Current specialist and recent handoffs from your self-hosted voice traces.";
6253
- var escapeHtml16 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
8101
+ var escapeHtml20 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6254
8102
  var labelFor = (current) => {
6255
8103
  if (!current)
6256
8104
  return "Waiting for specialist activity";
@@ -6277,24 +8125,24 @@ var renderVoiceAgentSquadStatusHTML = (snapshot, options = {}) => {
6277
8125
  const model = createVoiceAgentSquadStatusViewModel(snapshot, options);
6278
8126
  const current = model.current;
6279
8127
  const rows = model.sessions.length ? model.sessions.slice(0, 5).map((session) => `<li>
6280
- <span>${escapeHtml16(session.sessionId)}</span>
6281
- <strong>${escapeHtml16(session.targetAgentId ?? "none")}</strong>
6282
- <em>${escapeHtml16(session.status)}</em>
6283
- ${session.summary || session.reason ? `<p>${escapeHtml16(session.summary ?? session.reason ?? "")}</p>` : ""}
8128
+ <span>${escapeHtml20(session.sessionId)}</span>
8129
+ <strong>${escapeHtml20(session.targetAgentId ?? "none")}</strong>
8130
+ <em>${escapeHtml20(session.status)}</em>
8131
+ ${session.summary || session.reason ? `<p>${escapeHtml20(session.summary ?? session.reason ?? "")}</p>` : ""}
6284
8132
  </li>`).join("") : "<li><span>No squad traces yet.</span><strong>Waiting</strong></li>";
6285
8133
  return `<section class="absolute-voice-agent-squad-status">
6286
8134
  <header>
6287
- <span>${escapeHtml16(model.title)}</span>
6288
- <strong>${escapeHtml16(model.label)}</strong>
8135
+ <span>${escapeHtml20(model.title)}</span>
8136
+ <strong>${escapeHtml20(model.label)}</strong>
6289
8137
  </header>
6290
- <p>${escapeHtml16(model.description)}</p>
8138
+ <p>${escapeHtml20(model.description)}</p>
6291
8139
  <div>
6292
- <span>Session</span><strong>${escapeHtml16(current?.sessionId ?? "n/a")}</strong>
6293
- <span>From</span><strong>${escapeHtml16(current?.fromAgentId ?? "n/a")}</strong>
6294
- <span>Status</span><strong>${escapeHtml16(current?.status ?? "idle")}</strong>
8140
+ <span>Session</span><strong>${escapeHtml20(current?.sessionId ?? "n/a")}</strong>
8141
+ <span>From</span><strong>${escapeHtml20(current?.fromAgentId ?? "n/a")}</strong>
8142
+ <span>Status</span><strong>${escapeHtml20(current?.status ?? "idle")}</strong>
6295
8143
  </div>
6296
8144
  <ul>${rows}</ul>
6297
- ${model.error ? `<p class="absolute-voice-agent-squad-status__error">${escapeHtml16(model.error)}</p>` : ""}
8145
+ ${model.error ? `<p class="absolute-voice-agent-squad-status__error">${escapeHtml20(model.error)}</p>` : ""}
6298
8146
  </section>`;
6299
8147
  };
6300
8148
  var getVoiceAgentSquadStatusCSS = () => `.absolute-voice-agent-squad-status{border:1px solid #38bdf866;border-radius:20px;background:#0f172a;color:#f8fafc;padding:18px;font-family:inherit}.absolute-voice-agent-squad-status header{display:grid;gap:4px}.absolute-voice-agent-squad-status header span{color:#7dd3fc;font-size:12px;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-agent-squad-status header strong{font-size:20px}.absolute-voice-agent-squad-status p{color:#cbd5e1}.absolute-voice-agent-squad-status div{display:grid;gap:6px;grid-template-columns:max-content 1fr;margin:14px 0}.absolute-voice-agent-squad-status div span{color:#94a3b8}.absolute-voice-agent-squad-status ul{display:grid;gap:8px;list-style:none;margin:0;padding:0}.absolute-voice-agent-squad-status li{background:#020617;border:1px solid #1e293b;border-radius:14px;padding:10px}.absolute-voice-agent-squad-status li span{color:#94a3b8;display:block;font-size:12px}.absolute-voice-agent-squad-status li strong{display:block}.absolute-voice-agent-squad-status li em{color:#7dd3fc;font-style:normal}.absolute-voice-agent-squad-status__error{color:#fecaca;font-weight:800}`;
@@ -6531,7 +8379,7 @@ var useVoiceTurnLatency = (path = "/api/turn-latency", options = {}) => {
6531
8379
  var DEFAULT_TITLE15 = "Turn Latency";
6532
8380
  var DEFAULT_DESCRIPTION15 = "Per-turn timing from first transcript to commit and assistant response start.";
6533
8381
  var DEFAULT_PROOF_LABEL = "Run latency proof";
6534
- var escapeHtml17 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
8382
+ var escapeHtml21 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6535
8383
  var formatMs4 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
6536
8384
  var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
6537
8385
  const turns = (snapshot.report?.turns ?? []).map((turn) => ({
@@ -6559,25 +8407,25 @@ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
6559
8407
  };
6560
8408
  var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
6561
8409
  const model = createVoiceTurnLatencyViewModel(snapshot, options);
6562
- 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)}">
8410
+ 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--${escapeHtml21(turn.status)}">
6563
8411
  <header>
6564
- <strong>${escapeHtml17(turn.label)}</strong>
6565
- <span>${escapeHtml17(turn.status)}</span>
8412
+ <strong>${escapeHtml21(turn.label)}</strong>
8413
+ <span>${escapeHtml21(turn.status)}</span>
6566
8414
  </header>
6567
8415
  <dl>${turn.rows.map((row) => `<div>
6568
- <dt>${escapeHtml17(row.label)}</dt>
6569
- <dd>${escapeHtml17(row.value)}</dd>
8416
+ <dt>${escapeHtml21(row.label)}</dt>
8417
+ <dd>${escapeHtml21(row.value)}</dd>
6570
8418
  </div>`).join("")}</dl>
6571
8419
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-latency__empty">Complete a voice turn to see latency diagnostics.</p>';
6572
- return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml17(model.status)}">
8420
+ return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml21(model.status)}">
6573
8421
  <header class="absolute-voice-turn-latency__header">
6574
- <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml17(model.title)}</span>
6575
- <strong class="absolute-voice-turn-latency__label">${escapeHtml17(model.label)}</strong>
8422
+ <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml21(model.title)}</span>
8423
+ <strong class="absolute-voice-turn-latency__label">${escapeHtml21(model.label)}</strong>
6576
8424
  </header>
6577
- <p class="absolute-voice-turn-latency__description">${escapeHtml17(model.description)}</p>
6578
- ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml17(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
8425
+ <p class="absolute-voice-turn-latency__description">${escapeHtml21(model.description)}</p>
8426
+ ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml21(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
6579
8427
  ${turns}
6580
- ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml17(model.error)}</p>` : ""}
8428
+ ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml21(model.error)}</p>` : ""}
6581
8429
  </section>`;
6582
8430
  };
6583
8431
  var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {}) => {
@@ -6813,7 +8661,7 @@ var useVoiceTurnQuality = (path = "/api/turn-quality", options = {}) => {
6813
8661
  // src/client/turnQualityWidget.ts
6814
8662
  var DEFAULT_TITLE16 = "Turn Quality";
6815
8663
  var DEFAULT_DESCRIPTION16 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
6816
- var escapeHtml18 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
8664
+ var escapeHtml22 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6817
8665
  var formatConfidence = (value) => typeof value === "number" ? `${Math.round(value * 100)}%` : "n/a";
6818
8666
  var formatMaybe = (value) => value === undefined || value === "" ? "n/a" : String(value);
6819
8667
  var getTurnDetail = (turn) => {
@@ -6863,25 +8711,25 @@ var createVoiceTurnQualityViewModel = (snapshot, options = {}) => {
6863
8711
  };
6864
8712
  var renderVoiceTurnQualityHTML = (snapshot, options = {}) => {
6865
8713
  const model = createVoiceTurnQualityViewModel(snapshot, options);
6866
- 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)}">
8714
+ 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--${escapeHtml22(turn.status)}">
6867
8715
  <header>
6868
- <strong>${escapeHtml18(turn.label)}</strong>
6869
- <span>${escapeHtml18(turn.status)}</span>
8716
+ <strong>${escapeHtml22(turn.label)}</strong>
8717
+ <span>${escapeHtml22(turn.status)}</span>
6870
8718
  </header>
6871
- <p>${escapeHtml18(turn.detail)}</p>
8719
+ <p>${escapeHtml22(turn.detail)}</p>
6872
8720
  <dl>${turn.rows.map((row) => `<div>
6873
- <dt>${escapeHtml18(row.label)}</dt>
6874
- <dd>${escapeHtml18(row.value)}</dd>
8721
+ <dt>${escapeHtml22(row.label)}</dt>
8722
+ <dd>${escapeHtml22(row.value)}</dd>
6875
8723
  </div>`).join("")}</dl>
6876
8724
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-quality__empty">Complete a voice turn to see STT quality diagnostics.</p>';
6877
- return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml18(model.status)}">
8725
+ return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml22(model.status)}">
6878
8726
  <header class="absolute-voice-turn-quality__header">
6879
- <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml18(model.title)}</span>
6880
- <strong class="absolute-voice-turn-quality__label">${escapeHtml18(model.label)}</strong>
8727
+ <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml22(model.title)}</span>
8728
+ <strong class="absolute-voice-turn-quality__label">${escapeHtml22(model.label)}</strong>
6881
8729
  </header>
6882
- <p class="absolute-voice-turn-quality__description">${escapeHtml18(model.description)}</p>
8730
+ <p class="absolute-voice-turn-quality__description">${escapeHtml22(model.description)}</p>
6883
8731
  ${turns}
6884
- ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml18(model.error)}</p>` : ""}
8732
+ ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml22(model.error)}</p>` : ""}
6885
8733
  </section>`;
6886
8734
  };
6887
8735
  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}`;