@absolutejs/voice 0.0.22-beta.384 → 0.0.22-beta.386

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
  {
@@ -2273,6 +4026,228 @@ var appendRealCallRecoveryActionQuery = (href, query) => {
2273
4026
  const search = new URLSearchParams(entries).toString();
2274
4027
  return `${base}${separator}${search}${hash ? `#${hash}` : ""}`;
2275
4028
  };
4029
+ var sleepVoiceRealCallProfileRecoveryLoop = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
4030
+ var describeVoiceRealCallProfileRecoveryLoopAction = (action) => [
4031
+ action.label ?? action.id ?? "recovery action",
4032
+ action.profileId ? `profile=${action.profileId}` : undefined,
4033
+ action.href
4034
+ ].filter(Boolean).join(" ");
4035
+ var defaultVoiceRealCallProfileRecoveryLoopActionFilter = (action, readinessCheckLabel) => action.method?.toUpperCase() === "POST" && action.sourceCheckLabel === readinessCheckLabel && typeof action.href === "string" && action.href.length > 0;
4036
+ var uniqueVoiceRealCallProfileRecoveryLoopActions = (actions) => {
4037
+ const seen = new Set;
4038
+ return actions.filter((action) => {
4039
+ const key = `${action.method?.toUpperCase() ?? "GET"} ${action.href ?? ""}`;
4040
+ if (seen.has(key)) {
4041
+ return false;
4042
+ }
4043
+ seen.add(key);
4044
+ return true;
4045
+ });
4046
+ };
4047
+ var normalizeRealCallProfileRecoveryProvider = (role, provider) => {
4048
+ if (!provider) {
4049
+ return;
4050
+ }
4051
+ if (typeof provider === "string") {
4052
+ return {
4053
+ provider,
4054
+ selectedProvider: provider,
4055
+ status: "selected"
4056
+ };
4057
+ }
4058
+ return {
4059
+ ...provider,
4060
+ selectedProvider: provider.selectedProvider ?? provider.provider,
4061
+ status: provider.status ?? "selected"
4062
+ };
4063
+ };
4064
+ var profileRealCallRecoveryEvent = (event, profileId, metadata = {}) => ({
4065
+ ...event,
4066
+ metadata: {
4067
+ ...metadata,
4068
+ ...event.metadata ?? {},
4069
+ benchmarkProfileId: event.metadata?.benchmarkProfileId ?? profileId,
4070
+ profileId: event.metadata?.profileId ?? profileId
4071
+ },
4072
+ payload: {
4073
+ ...event.payload ?? {},
4074
+ benchmarkProfileId: event.payload.benchmarkProfileId ?? profileId,
4075
+ profileId: event.payload.profileId ?? profileId
4076
+ }
4077
+ });
4078
+ var appendVoiceRealCallProfileRecoveryEvidence = async (options) => {
4079
+ const at = options.at ?? Date.now();
4080
+ const scenarioId = options.scenarioId ?? "real-call-profile-recovery";
4081
+ const sessionId = options.sessionId ?? `real-call-profile-recovery-${options.profileId}-${new Date(at).toISOString()}`;
4082
+ const browser = options.browser ?? {};
4083
+ const live = options.live ?? {};
4084
+ const providerEvents = [
4085
+ ["llm", 320, "live-call"],
4086
+ ["stt", 82, "live-stt"],
4087
+ ["tts", 45, "live-tts"]
4088
+ ].flatMap(([role, defaultElapsedMs, defaultSurface], index) => {
4089
+ const provider = normalizeRealCallProfileRecoveryProvider(role, options.providers?.[role]);
4090
+ if (!provider) {
4091
+ return [];
4092
+ }
4093
+ return [
4094
+ profileRealCallRecoveryEvent(createVoiceProviderDecisionTraceEvent({
4095
+ at: at + index,
4096
+ elapsedMs: provider.elapsedMs ?? defaultElapsedMs,
4097
+ kind: role,
4098
+ provider: provider.provider,
4099
+ reason: provider.reason ?? `Real-call profile recovery selected ${provider.provider} for ${role.toUpperCase()} evidence.`,
4100
+ scenarioId,
4101
+ selectedProvider: provider.selectedProvider,
4102
+ sessionId,
4103
+ status: provider.status,
4104
+ surface: provider.surface ?? defaultSurface
4105
+ }), options.profileId, options.metadata)
4106
+ ];
4107
+ });
4108
+ const browserEvents = browser === false ? [] : [
4109
+ profileRealCallRecoveryEvent(createVoiceTraceEvent({
4110
+ at: at + 3,
4111
+ payload: {
4112
+ firstAudioLatencyMs: browser.firstAudioLatencyMs ?? 420,
4113
+ messageCount: browser.messageCount,
4114
+ openSockets: browser.openSockets,
4115
+ receivedBytes: browser.receivedBytes,
4116
+ sentBytes: browser.sentBytes,
4117
+ status: browser.status ?? "pass"
4118
+ },
4119
+ scenarioId,
4120
+ sessionId,
4121
+ type: "client.browser_media"
4122
+ }), options.profileId, options.metadata)
4123
+ ];
4124
+ const liveEvents = live === false ? [] : [
4125
+ profileRealCallRecoveryEvent(createVoiceTraceEvent({
4126
+ at: at + 4,
4127
+ payload: {
4128
+ latencyMs: live.latencyMs ?? 420,
4129
+ status: live.status ?? "pass"
4130
+ },
4131
+ scenarioId,
4132
+ sessionId,
4133
+ type: "client.live_latency"
4134
+ }), options.profileId, options.metadata)
4135
+ ];
4136
+ const events = await Promise.all([...providerEvents, ...browserEvents, ...liveEvents].map((event) => options.store.append(event)));
4137
+ return { events, sessionId };
4138
+ };
4139
+ var runVoiceRealCallProfileRecoveryLoop = async (options) => {
4140
+ const baseUrl = options.baseUrl.replace(/\/$/, "");
4141
+ const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
4142
+ const jobPollMs = options.jobPollMs ?? 1200;
4143
+ const jobTimeoutMs = options.jobTimeoutMs ?? 600000;
4144
+ const readinessCheckLabel = options.readinessCheckLabel ?? "Real-call profile history";
4145
+ const fetchImpl = options.fetch ?? fetch;
4146
+ const recoveryActionsHref = options.recoveryActionsHref ?? "/api/production-readiness/recovery-actions";
4147
+ const readinessHref = options.readinessHref ?? "/api/production-readiness";
4148
+ const refreshHref = options.refreshHref === undefined ? "/api/voice/real-call-profile-history/refresh" : options.refreshHref;
4149
+ const jobHref = options.jobHref ?? "/api/voice/real-call-profile-history/actions";
4150
+ const toAbsoluteUrl = (href) => new URL(href, baseUrl).toString();
4151
+ const parseJson = async (response) => {
4152
+ const text = await response.text();
4153
+ try {
4154
+ return JSON.parse(text);
4155
+ } catch (error) {
4156
+ throw new Error(`Expected JSON from ${response.url}, got: ${text.slice(0, 300)}`, { cause: error });
4157
+ }
4158
+ };
4159
+ const fetchJson = async (href, init) => {
4160
+ const response = await fetchImpl(toAbsoluteUrl(href), {
4161
+ headers: { accept: "application/json", ...init?.headers },
4162
+ ...init,
4163
+ signal: init?.signal ?? AbortSignal.timeout(requestTimeoutMs)
4164
+ });
4165
+ if (!response.ok) {
4166
+ throw new Error(`${href} returned HTTP ${String(response.status)}.`);
4167
+ }
4168
+ return parseJson(response);
4169
+ };
4170
+ const resolveJobHref = (jobId) => typeof jobHref === "function" ? jobHref(jobId) : `${jobHref.replace(/\/$/, "")}/${jobId}`;
4171
+ const getGate = async (fresh = false) => {
4172
+ const href = fresh ? `${readinessHref}${readinessHref.includes("?") ? "&" : "?"}voiceRecoveryLoopFresh=${String(Date.now())}` : readinessHref;
4173
+ const readiness = await fetchJson(href);
4174
+ return readiness.checks?.find((check) => check.label === readinessCheckLabel) ?? null;
4175
+ };
4176
+ const actionsResponse = await fetchJson(recoveryActionsHref);
4177
+ const actionFilter = options.actionFilter ?? ((action) => defaultVoiceRealCallProfileRecoveryLoopActionFilter(action, readinessCheckLabel));
4178
+ const actions = uniqueVoiceRealCallProfileRecoveryLoopActions((actionsResponse.actions ?? []).filter(actionFilter));
4179
+ if (actions.length === 0) {
4180
+ const realCallProfileGate2 = await getGate();
4181
+ return {
4182
+ actionCount: 0,
4183
+ actions,
4184
+ jobs: [],
4185
+ ok: realCallProfileGate2?.status === "pass",
4186
+ realCallProfileGate: realCallProfileGate2,
4187
+ startFailures: []
4188
+ };
4189
+ }
4190
+ options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel.`);
4191
+ for (const action of actions) {
4192
+ options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
4193
+ }
4194
+ const starts = await Promise.allSettled(actions.map(async (action) => {
4195
+ if (!action.href) {
4196
+ throw new Error("Recovery action is missing href.");
4197
+ }
4198
+ const body = await fetchJson(action.href, { method: "POST" });
4199
+ return { action, ...body };
4200
+ }));
4201
+ const startedJobs = starts.flatMap((result) => {
4202
+ if (result.status === "rejected") {
4203
+ return [];
4204
+ }
4205
+ return result.value.jobId ? [result.value] : [];
4206
+ });
4207
+ const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
4208
+ {
4209
+ action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
4210
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason)
4211
+ }
4212
+ ] : []);
4213
+ const pollJob = async (jobId) => {
4214
+ const deadline = Date.now() + jobTimeoutMs;
4215
+ while (Date.now() < deadline) {
4216
+ const body = await fetchJson(resolveJobHref(jobId));
4217
+ const job = body.job;
4218
+ if (!job) {
4219
+ throw new Error(`Recovery job ${jobId} was not found.`);
4220
+ }
4221
+ if (job.status === "pass" || job.status === "fail") {
4222
+ return job;
4223
+ }
4224
+ await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
4225
+ }
4226
+ throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
4227
+ };
4228
+ options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
4229
+ const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
4230
+ const jobs = jobResults.map((result, index) => ({
4231
+ action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
4232
+ jobId: startedJobs[index]?.jobId,
4233
+ result: result.status === "fulfilled" ? result.value : {
4234
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason),
4235
+ status: "fail"
4236
+ }
4237
+ }));
4238
+ if (refreshHref !== false) {
4239
+ await fetchJson(refreshHref, { method: "POST" });
4240
+ }
4241
+ const realCallProfileGate = await getGate(true);
4242
+ return {
4243
+ actionCount: actions.length,
4244
+ actions,
4245
+ jobs,
4246
+ ok: startFailures.length === 0 && jobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
4247
+ realCallProfileGate,
4248
+ startFailures
4249
+ };
4250
+ };
2276
4251
  var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
2277
4252
  const actions = [
2278
4253
  {
@@ -2989,7 +4964,7 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
2989
4964
  }
2990
4965
  };
2991
4966
  };
2992
- var escapeHtml5 = (value) => String(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4967
+ var escapeHtml9 = (value) => String(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2993
4968
  var escapeMarkdown = (value) => value.replaceAll("|", "\\|");
2994
4969
  var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provider Runtime Recommendations") => [
2995
4970
  `# ${title}`,
@@ -3022,11 +4997,11 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
3022
4997
  ].join(`
3023
4998
  `);
3024
4999
  var renderVoiceProofTrendRecommendationHTML = (report, title = "Voice Provider Runtime Recommendations") => {
3025
- 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("");
3026
- const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml5(issue)}</li>`).join("");
3027
- 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("");
3028
- 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("");
3029
- 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>`;
5000
+ 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("");
5001
+ const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml9(issue)}</li>`).join("");
5002
+ 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("");
5003
+ 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("");
5004
+ 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>`;
3030
5005
  };
3031
5006
  var renderVoiceRealCallProfileHistoryMarkdown = (report, title = "Voice Real-Call Profile History") => [
3032
5007
  `# ${title}`,
@@ -3060,18 +5035,18 @@ var renderVoiceRealCallProfileHistoryMarkdown = (report, title = "Voice Real-Cal
3060
5035
  ].join(`
3061
5036
  `);
3062
5037
  var renderVoiceRealCallProfileHistoryHTML = (report, title = "Voice Real-Call Profile History") => {
3063
- 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>';
3064
- 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>';
3065
- 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("");
3066
- const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml5(issue)}</li>`).join("");
3067
- 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>`;
5038
+ 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>';
5039
+ 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>';
5040
+ 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("");
5041
+ const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml9(issue)}</li>`).join("");
5042
+ 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>`;
3068
5043
  };
3069
5044
  var createVoiceProofTrendRecommendationRoutes = (options) => {
3070
5045
  const path = options.path ?? "/api/voice/proof-trend-recommendations";
3071
5046
  const htmlPath = options.htmlPath === undefined ? "/voice/proof-trend-recommendations" : options.htmlPath;
3072
5047
  const markdownPath = options.markdownPath === undefined ? "/voice/proof-trend-recommendations.md" : options.markdownPath;
3073
5048
  const title = options.title ?? "Voice Provider Runtime Recommendations";
3074
- const routes = new Elysia({
5049
+ const routes = new Elysia4({
3075
5050
  name: options.name ?? "absolutejs-voice-proof-trend-recommendations"
3076
5051
  });
3077
5052
  const loadReport = async () => {
@@ -3113,7 +5088,7 @@ var createVoiceRealCallProfileHistoryRoutes = (options = {}) => {
3113
5088
  const htmlPath = options.htmlPath === undefined ? "/voice/real-call-profile-history" : options.htmlPath;
3114
5089
  const markdownPath = options.markdownPath === undefined ? "/voice/real-call-profile-history.md" : options.markdownPath;
3115
5090
  const title = options.title ?? "Voice Real-Call Profile History";
3116
- const routes = new Elysia({
5091
+ const routes = new Elysia4({
3117
5092
  name: options.name ?? "absolutejs-voice-real-call-profile-history"
3118
5093
  });
3119
5094
  const loadReport = async () => {
@@ -3165,7 +5140,7 @@ var loadVoiceRealCallProfileHistoryRouteReport = async (options) => {
3165
5140
  };
3166
5141
  var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
3167
5142
  const path = options.path ?? "/api/voice/real-call-profile-history";
3168
- const routes = new Elysia({
5143
+ const routes = new Elysia4({
3169
5144
  name: options.name ?? "absolutejs-voice-real-call-profile-recovery-actions"
3170
5145
  });
3171
5146
  const actionPath = (actionId) => `${path}${realCallProfileActionPaths[actionId]}`;
@@ -3340,7 +5315,7 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
3340
5315
  };
3341
5316
  var createVoiceProofTrendRoutes = (options) => {
3342
5317
  const path = options.path ?? "/api/voice/proof-trends";
3343
- const routes = new Elysia({
5318
+ const routes = new Elysia4({
3344
5319
  name: options.name ?? "absolutejs-voice-proof-trends"
3345
5320
  });
3346
5321
  routes.get(path, async () => {
@@ -3380,7 +5355,7 @@ var DEFAULT_LINKS2 = [
3380
5355
  { href: "/voice/proof-trends", label: "Trend page" },
3381
5356
  { href: "/api/voice/proof-trends", label: "Trend JSON" }
3382
5357
  ];
3383
- var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5358
+ var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3384
5359
  var formatMs = (value) => typeof value === "number" && Number.isFinite(value) ? `${Math.round(value)}ms` : "n/a";
3385
5360
  var statusLabel = (report) => {
3386
5361
  if (!report) {
@@ -3430,19 +5405,19 @@ var createVoiceProofTrendsViewModel = (snapshot, options = {}) => {
3430
5405
  var renderVoiceProofTrendsHTML = (snapshot, options = {}) => {
3431
5406
  const model = createVoiceProofTrendsViewModel(snapshot, options);
3432
5407
  const metrics = model.metrics.length ? `<div class="absolute-voice-proof-trends__metrics">${model.metrics.map((metric) => `<article>
3433
- <span>${escapeHtml6(metric.label)}</span>
3434
- <strong>${escapeHtml6(metric.value)}</strong>
3435
- </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>`;
3436
- 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>` : "";
3437
- return `<section class="absolute-voice-proof-trends absolute-voice-proof-trends--${escapeHtml6(model.status)}">
5408
+ <span>${escapeHtml10(metric.label)}</span>
5409
+ <strong>${escapeHtml10(metric.value)}</strong>
5410
+ </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>`;
5411
+ 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>` : "";
5412
+ return `<section class="absolute-voice-proof-trends absolute-voice-proof-trends--${escapeHtml10(model.status)}">
3438
5413
  <header class="absolute-voice-proof-trends__header">
3439
- <span class="absolute-voice-proof-trends__eyebrow">${escapeHtml6(model.title)}</span>
3440
- <strong class="absolute-voice-proof-trends__label">${escapeHtml6(model.label)}</strong>
5414
+ <span class="absolute-voice-proof-trends__eyebrow">${escapeHtml10(model.title)}</span>
5415
+ <strong class="absolute-voice-proof-trends__label">${escapeHtml10(model.label)}</strong>
3441
5416
  </header>
3442
- <p class="absolute-voice-proof-trends__description">${escapeHtml6(model.description)}</p>
5417
+ <p class="absolute-voice-proof-trends__description">${escapeHtml10(model.description)}</p>
3443
5418
  ${metrics}
3444
5419
  ${links}
3445
- ${model.error ? `<p class="absolute-voice-proof-trends__error">${escapeHtml6(model.error)}</p>` : ""}
5420
+ ${model.error ? `<p class="absolute-voice-proof-trends__error">${escapeHtml10(model.error)}</p>` : ""}
3446
5421
  </section>`;
3447
5422
  };
3448
5423
  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}`;
@@ -3627,7 +5602,7 @@ var DEFAULT_LINKS3 = [
3627
5602
  { href: "/voice/real-call-profile-history", label: "Profile history" },
3628
5603
  { href: "/api/voice/real-call-profile-history", label: "JSON" }
3629
5604
  ];
3630
- var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5605
+ var escapeHtml11 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3631
5606
  var formatMs2 = (value) => typeof value === "number" && Number.isFinite(value) ? `${Math.round(value)}ms` : "n/a";
3632
5607
  var formatProviderRoutes = (profile) => Object.entries(profile.providerRoutes).map(([role, provider]) => `${role}: ${provider}`).join(", ") || "No complete route yet";
3633
5608
  var createProfileView = (profile) => ({
@@ -3658,25 +5633,25 @@ var createVoiceProfileComparisonViewModel = (snapshot, options = {}) => {
3658
5633
  };
3659
5634
  var renderVoiceProfileComparisonHTML = (snapshot, options = {}) => {
3660
5635
  const model = createVoiceProfileComparisonViewModel(snapshot, options);
3661
- 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)}">
5636
+ 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)}">
3662
5637
  <header>
3663
- <span>${escapeHtml7(profile.status)}</span>
3664
- <strong>${escapeHtml7(profile.label)}</strong>
5638
+ <span>${escapeHtml11(profile.status)}</span>
5639
+ <strong>${escapeHtml11(profile.label)}</strong>
3665
5640
  </header>
3666
- <p>${escapeHtml7(profile.providerRoutes)}</p>
3667
- <div>${profile.evidence.map((metric) => `<span><small>${escapeHtml7(metric.label)}</small><b>${escapeHtml7(metric.value)}</b></span>`).join("")}</div>
3668
- <em>${escapeHtml7(profile.nextMove)}</em>
3669
- </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>`;
3670
- 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>` : "";
3671
- return `<section class="absolute-voice-profile-comparison absolute-voice-profile-comparison--${escapeHtml7(model.status)}">
5641
+ <p>${escapeHtml11(profile.providerRoutes)}</p>
5642
+ <div>${profile.evidence.map((metric) => `<span><small>${escapeHtml11(metric.label)}</small><b>${escapeHtml11(metric.value)}</b></span>`).join("")}</div>
5643
+ <em>${escapeHtml11(profile.nextMove)}</em>
5644
+ </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>`;
5645
+ 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>` : "";
5646
+ return `<section class="absolute-voice-profile-comparison absolute-voice-profile-comparison--${escapeHtml11(model.status)}">
3672
5647
  <header class="absolute-voice-profile-comparison__header">
3673
- <span class="absolute-voice-profile-comparison__eyebrow">${escapeHtml7(model.title)}</span>
3674
- <strong class="absolute-voice-profile-comparison__label">${escapeHtml7(model.label)}</strong>
5648
+ <span class="absolute-voice-profile-comparison__eyebrow">${escapeHtml11(model.title)}</span>
5649
+ <strong class="absolute-voice-profile-comparison__label">${escapeHtml11(model.label)}</strong>
3675
5650
  </header>
3676
- <p class="absolute-voice-profile-comparison__description">${escapeHtml7(model.description)}</p>
5651
+ <p class="absolute-voice-profile-comparison__description">${escapeHtml11(model.description)}</p>
3677
5652
  ${profiles}
3678
5653
  ${links}
3679
- ${model.error ? `<p class="absolute-voice-profile-comparison__error">${escapeHtml7(model.error)}</p>` : ""}
5654
+ ${model.error ? `<p class="absolute-voice-profile-comparison__error">${escapeHtml11(model.error)}</p>` : ""}
3680
5655
  </section>`;
3681
5656
  };
3682
5657
  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}`;
@@ -3899,27 +5874,27 @@ var createVoiceProfileSwitchRecommendationStore = (path = "/api/voice/profile-sw
3899
5874
  // src/client/profileSwitchRecommendationWidget.ts
3900
5875
  var DEFAULT_TITLE7 = "Profile Switch Recommendation";
3901
5876
  var DEFAULT_DESCRIPTION7 = "Compares the current session against measured profile evidence and recommends whether to switch stacks.";
3902
- var escapeHtml8 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5877
+ var escapeHtml12 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3903
5878
  var formatRoute = (routes) => routes ? Object.entries(routes).map(([role, provider]) => `${role}: ${provider}`).join(", ") : "No route";
3904
5879
  var renderVoiceProfileSwitchRecommendationHTML = (snapshot, options = {}) => {
3905
5880
  const recommendation = snapshot.recommendation;
3906
5881
  const status = snapshot.error ? "error" : recommendation ? recommendation.status : snapshot.isLoading ? "loading" : "empty";
3907
5882
  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";
3908
5883
  const body = recommendation ? `<div class="absolute-voice-profile-switch__body">
3909
- <p><strong>Current:</strong> ${escapeHtml8(recommendation.currentProfile?.label ?? recommendation.currentProfile?.profileId ?? "Unknown")}</p>
3910
- <p><strong>Recommended:</strong> ${escapeHtml8(recommendation.recommendedProfile?.label ?? recommendation.recommendedProfile?.profileId ?? "None")}</p>
3911
- <p><strong>Routes:</strong> ${escapeHtml8(formatRoute(recommendation.recommendedProfile?.providerRoutes))}</p>
3912
- <ul>${recommendation.reasons.map((reason) => `<li>${escapeHtml8(reason)}</li>`).join("")}</ul>
3913
- <em>${escapeHtml8(recommendation.nextMove)}</em>
3914
- </div>` : `<p class="absolute-voice-profile-switch__empty">${escapeHtml8(snapshot.error ?? "Run session traffic to populate a recommendation.")}</p>`;
3915
- return `<section class="absolute-voice-profile-switch absolute-voice-profile-switch--${escapeHtml8(status)}">
5884
+ <p><strong>Current:</strong> ${escapeHtml12(recommendation.currentProfile?.label ?? recommendation.currentProfile?.profileId ?? "Unknown")}</p>
5885
+ <p><strong>Recommended:</strong> ${escapeHtml12(recommendation.recommendedProfile?.label ?? recommendation.recommendedProfile?.profileId ?? "None")}</p>
5886
+ <p><strong>Routes:</strong> ${escapeHtml12(formatRoute(recommendation.recommendedProfile?.providerRoutes))}</p>
5887
+ <ul>${recommendation.reasons.map((reason) => `<li>${escapeHtml12(reason)}</li>`).join("")}</ul>
5888
+ <em>${escapeHtml12(recommendation.nextMove)}</em>
5889
+ </div>` : `<p class="absolute-voice-profile-switch__empty">${escapeHtml12(snapshot.error ?? "Run session traffic to populate a recommendation.")}</p>`;
5890
+ return `<section class="absolute-voice-profile-switch absolute-voice-profile-switch--${escapeHtml12(status)}">
3916
5891
  <header class="absolute-voice-profile-switch__header">
3917
- <span class="absolute-voice-profile-switch__eyebrow">${escapeHtml8(options.title ?? DEFAULT_TITLE7)}</span>
3918
- <strong class="absolute-voice-profile-switch__label">${escapeHtml8(label)}</strong>
5892
+ <span class="absolute-voice-profile-switch__eyebrow">${escapeHtml12(options.title ?? DEFAULT_TITLE7)}</span>
5893
+ <strong class="absolute-voice-profile-switch__label">${escapeHtml12(label)}</strong>
3919
5894
  </header>
3920
- <p class="absolute-voice-profile-switch__description">${escapeHtml8(options.description ?? DEFAULT_DESCRIPTION7)}</p>
5895
+ <p class="absolute-voice-profile-switch__description">${escapeHtml12(options.description ?? DEFAULT_DESCRIPTION7)}</p>
3921
5896
  ${body}
3922
- ${snapshot.error ? `<p class="absolute-voice-profile-switch__error">${escapeHtml8(snapshot.error)}</p>` : ""}
5897
+ ${snapshot.error ? `<p class="absolute-voice-profile-switch__error">${escapeHtml12(snapshot.error)}</p>` : ""}
3923
5898
  </section>`;
3924
5899
  };
3925
5900
  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}`;
@@ -4074,7 +6049,7 @@ var DEFAULT_LINKS4 = [
4074
6049
  { href: "/production-readiness", label: "Readiness page" },
4075
6050
  { href: "/voice/slo-readiness-thresholds", label: "Gate thresholds" }
4076
6051
  ];
4077
- var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6052
+ var escapeHtml13 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4078
6053
  var formatExplanationValue = (value, unit) => {
4079
6054
  if (value === undefined || value === null) {
4080
6055
  return "n/a";
@@ -4115,23 +6090,23 @@ var createVoiceReadinessFailuresViewModel = (snapshot, options = {}) => {
4115
6090
  };
4116
6091
  var renderVoiceReadinessFailuresHTML = (snapshot, options = {}) => {
4117
6092
  const model = createVoiceReadinessFailuresViewModel(snapshot, options);
4118
- 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)}">
4119
- <span>${escapeHtml9(failure.status.toUpperCase())}</span>
4120
- <strong>${escapeHtml9(failure.label)}</strong>
4121
- <p>Observed ${escapeHtml9(failure.observed)} against ${escapeHtml9(failure.thresholdLabel)} ${escapeHtml9(failure.threshold)}.</p>
4122
- <p>${escapeHtml9(failure.remediation)}</p>
4123
- <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>
4124
- </article>`).join("")}</div>` : `<p class="absolute-voice-readiness-failures__empty">${model.error ? escapeHtml9(model.error) : "No calibrated readiness gate explanations are open."}</p>`;
4125
- 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>` : "";
4126
- return `<section class="absolute-voice-readiness-failures absolute-voice-readiness-failures--${escapeHtml9(model.status)}">
6093
+ 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)}">
6094
+ <span>${escapeHtml13(failure.status.toUpperCase())}</span>
6095
+ <strong>${escapeHtml13(failure.label)}</strong>
6096
+ <p>Observed ${escapeHtml13(failure.observed)} against ${escapeHtml13(failure.thresholdLabel)} ${escapeHtml13(failure.threshold)}.</p>
6097
+ <p>${escapeHtml13(failure.remediation)}</p>
6098
+ <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>
6099
+ </article>`).join("")}</div>` : `<p class="absolute-voice-readiness-failures__empty">${model.error ? escapeHtml13(model.error) : "No calibrated readiness gate explanations are open."}</p>`;
6100
+ 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>` : "";
6101
+ return `<section class="absolute-voice-readiness-failures absolute-voice-readiness-failures--${escapeHtml13(model.status)}">
4127
6102
  <header class="absolute-voice-readiness-failures__header">
4128
- <span class="absolute-voice-readiness-failures__eyebrow">${escapeHtml9(model.title)}</span>
4129
- <strong class="absolute-voice-readiness-failures__label">${escapeHtml9(model.label)}</strong>
6103
+ <span class="absolute-voice-readiness-failures__eyebrow">${escapeHtml13(model.title)}</span>
6104
+ <strong class="absolute-voice-readiness-failures__label">${escapeHtml13(model.label)}</strong>
4130
6105
  </header>
4131
- <p class="absolute-voice-readiness-failures__description">${escapeHtml9(model.description)}</p>
6106
+ <p class="absolute-voice-readiness-failures__description">${escapeHtml13(model.description)}</p>
4132
6107
  ${failures}
4133
6108
  ${links}
4134
- ${model.error ? `<p class="absolute-voice-readiness-failures__error">${escapeHtml9(model.error)}</p>` : ""}
6109
+ ${model.error ? `<p class="absolute-voice-readiness-failures__error">${escapeHtml13(model.error)}</p>` : ""}
4135
6110
  </section>`;
4136
6111
  };
4137
6112
  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}`;
@@ -4360,7 +6335,7 @@ var createVoiceProviderSimulationControlsStore = (options) => {
4360
6335
  };
4361
6336
 
4362
6337
  // src/client/providerSimulationControlsWidget.ts
4363
- var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6338
+ var escapeHtml14 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4364
6339
  var formatKind = (kind) => (kind ?? "stt").toUpperCase();
4365
6340
  var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
4366
6341
  const configuredProviders = options.providers.filter((provider) => provider.configured !== false);
@@ -4380,18 +6355,18 @@ var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
4380
6355
  };
4381
6356
  var renderVoiceProviderSimulationControlsHTML = (snapshot, options) => {
4382
6357
  const model = createVoiceProviderSimulationControlsViewModel(snapshot, options);
4383
- 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("");
4384
- 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("");
6358
+ 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("");
6359
+ 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("");
4385
6360
  return `<section class="absolute-voice-provider-simulation absolute-voice-provider-simulation--${snapshot.error ? "error" : snapshot.isRunning ? "running" : "ready"}">
4386
6361
  <header class="absolute-voice-provider-simulation__header">
4387
- <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml10(model.title)}</span>
4388
- <strong class="absolute-voice-provider-simulation__label">${escapeHtml10(model.label)}</strong>
6362
+ <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml14(model.title)}</span>
6363
+ <strong class="absolute-voice-provider-simulation__label">${escapeHtml14(model.label)}</strong>
4389
6364
  </header>
4390
- <p class="absolute-voice-provider-simulation__description">${escapeHtml10(model.description)}</p>
4391
- ${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml10(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
6365
+ <p class="absolute-voice-provider-simulation__description">${escapeHtml14(model.description)}</p>
6366
+ ${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml14(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
4392
6367
  <div class="absolute-voice-provider-simulation__actions">${failureButtons}${recoveryButtons}</div>
4393
- ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml10(snapshot.error)}</p>` : ""}
4394
- ${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml10(model.resultText)}</pre>` : ""}
6368
+ ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml14(snapshot.error)}</p>` : ""}
6369
+ ${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml14(model.resultText)}</pre>` : ""}
4395
6370
  </section>`;
4396
6371
  };
4397
6372
  var bindVoiceProviderSimulationControls = (element, store) => {
@@ -4651,7 +6626,7 @@ var useVoiceProviderCapabilities = (path = "/api/provider-capabilities", options
4651
6626
  // src/client/providerCapabilitiesWidget.ts
4652
6627
  var DEFAULT_TITLE9 = "Provider Capabilities";
4653
6628
  var DEFAULT_DESCRIPTION9 = "Configured, selected, and healthy voice providers for this deployment.";
4654
- var escapeHtml11 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6629
+ var escapeHtml15 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4655
6630
  var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
4656
6631
  var formatKind2 = (kind) => kind.toUpperCase();
4657
6632
  var formatStatus2 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
@@ -4706,25 +6681,25 @@ var createVoiceProviderCapabilitiesViewModel = (snapshot, options = {}) => {
4706
6681
  };
4707
6682
  var renderVoiceProviderCapabilitiesHTML = (snapshot, options = {}) => {
4708
6683
  const model = createVoiceProviderCapabilitiesViewModel(snapshot, options);
4709
- 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)}">
6684
+ 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)}">
4710
6685
  <header>
4711
- <strong>${escapeHtml11(capability.label)}</strong>
4712
- <span>${escapeHtml11(formatStatus2(capability.status))}</span>
6686
+ <strong>${escapeHtml15(capability.label)}</strong>
6687
+ <span>${escapeHtml15(formatStatus2(capability.status))}</span>
4713
6688
  </header>
4714
- <p>${escapeHtml11(capability.detail)}</p>
6689
+ <p>${escapeHtml15(capability.detail)}</p>
4715
6690
  <dl>${capability.rows.map((row) => `<div>
4716
- <dt>${escapeHtml11(row.label)}</dt>
4717
- <dd>${escapeHtml11(row.value)}</dd>
6691
+ <dt>${escapeHtml15(row.label)}</dt>
6692
+ <dd>${escapeHtml15(row.value)}</dd>
4718
6693
  </div>`).join("")}</dl>
4719
6694
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-capabilities__empty">Configure provider capabilities to see deployment coverage.</p>';
4720
- return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml11(model.status)}">
6695
+ return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml15(model.status)}">
4721
6696
  <header class="absolute-voice-provider-capabilities__header">
4722
- <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml11(model.title)}</span>
4723
- <strong class="absolute-voice-provider-capabilities__label">${escapeHtml11(model.label)}</strong>
6697
+ <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml15(model.title)}</span>
6698
+ <strong class="absolute-voice-provider-capabilities__label">${escapeHtml15(model.label)}</strong>
4724
6699
  </header>
4725
- <p class="absolute-voice-provider-capabilities__description">${escapeHtml11(model.description)}</p>
6700
+ <p class="absolute-voice-provider-capabilities__description">${escapeHtml15(model.description)}</p>
4726
6701
  ${capabilities}
4727
- ${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml11(model.error)}</p>` : ""}
6702
+ ${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml15(model.error)}</p>` : ""}
4728
6703
  </section>`;
4729
6704
  };
4730
6705
  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}`;
@@ -4942,7 +6917,7 @@ var useVoiceProviderContracts = (path = "/api/provider-contracts", options = {})
4942
6917
  // src/client/providerContractsWidget.ts
4943
6918
  var DEFAULT_TITLE10 = "Provider Contracts";
4944
6919
  var DEFAULT_DESCRIPTION10 = "Production contract coverage for provider env, latency, fallback, streaming, and capabilities.";
4945
- var escapeHtml12 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6920
+ var escapeHtml16 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4946
6921
  var formatProvider2 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
4947
6922
  var formatStatus3 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
4948
6923
  var contractDetail = (row) => {
@@ -4986,26 +6961,26 @@ var createVoiceProviderContractsViewModel = (snapshot, options = {}) => {
4986
6961
  };
4987
6962
  var renderVoiceProviderContractsHTML = (snapshot, options = {}) => {
4988
6963
  const model = createVoiceProviderContractsViewModel(snapshot, options);
4989
- 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)}">
6964
+ 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)}">
4990
6965
  <header>
4991
- <strong>${escapeHtml12(row.label)}</strong>
4992
- <span>${escapeHtml12(formatStatus3(row.status))}</span>
6966
+ <strong>${escapeHtml16(row.label)}</strong>
6967
+ <span>${escapeHtml16(formatStatus3(row.status))}</span>
4993
6968
  </header>
4994
- <p>${escapeHtml12(row.detail)}</p>
4995
- ${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>` : ""}
6969
+ <p>${escapeHtml16(row.detail)}</p>
6970
+ ${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>` : ""}
4996
6971
  <dl>${row.rows.map((item) => `<div>
4997
- <dt>${escapeHtml12(item.label)}</dt>
4998
- <dd>${escapeHtml12(item.value)}</dd>
6972
+ <dt>${escapeHtml16(item.label)}</dt>
6973
+ <dd>${escapeHtml16(item.value)}</dd>
4999
6974
  </div>`).join("")}</dl>
5000
6975
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-contracts__empty">Configure provider contracts to see production coverage.</p>';
5001
- return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${escapeHtml12(model.status)}">
6976
+ return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${escapeHtml16(model.status)}">
5002
6977
  <header class="absolute-voice-provider-contracts__header">
5003
- <span class="absolute-voice-provider-contracts__eyebrow">${escapeHtml12(model.title)}</span>
5004
- <strong class="absolute-voice-provider-contracts__label">${escapeHtml12(model.label)}</strong>
6978
+ <span class="absolute-voice-provider-contracts__eyebrow">${escapeHtml16(model.title)}</span>
6979
+ <strong class="absolute-voice-provider-contracts__label">${escapeHtml16(model.label)}</strong>
5005
6980
  </header>
5006
- <p class="absolute-voice-provider-contracts__description">${escapeHtml12(model.description)}</p>
6981
+ <p class="absolute-voice-provider-contracts__description">${escapeHtml16(model.description)}</p>
5007
6982
  ${rows}
5008
- ${model.error ? `<p class="absolute-voice-provider-contracts__error">${escapeHtml12(model.error)}</p>` : ""}
6983
+ ${model.error ? `<p class="absolute-voice-provider-contracts__error">${escapeHtml16(model.error)}</p>` : ""}
5009
6984
  </section>`;
5010
6985
  };
5011
6986
  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}`;
@@ -5244,7 +7219,7 @@ var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
5244
7219
  // src/client/providerStatusWidget.ts
5245
7220
  var DEFAULT_TITLE11 = "Voice Providers";
5246
7221
  var DEFAULT_DESCRIPTION11 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
5247
- var escapeHtml13 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
7222
+ var escapeHtml17 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5248
7223
  var formatProvider3 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
5249
7224
  var formatStatus4 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
5250
7225
  var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
@@ -5300,25 +7275,25 @@ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
5300
7275
  };
5301
7276
  var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
5302
7277
  const model = createVoiceProviderStatusViewModel(snapshot, options);
5303
- 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)}">
7278
+ 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)}">
5304
7279
  <header>
5305
- <strong>${escapeHtml13(provider.label)}</strong>
5306
- <span>${escapeHtml13(formatStatus4(provider.status))}</span>
7280
+ <strong>${escapeHtml17(provider.label)}</strong>
7281
+ <span>${escapeHtml17(formatStatus4(provider.status))}</span>
5307
7282
  </header>
5308
- <p>${escapeHtml13(provider.detail)}</p>
7283
+ <p>${escapeHtml17(provider.detail)}</p>
5309
7284
  <dl>${provider.rows.map((row) => `<div>
5310
- <dt>${escapeHtml13(row.label)}</dt>
5311
- <dd>${escapeHtml13(row.value)}</dd>
7285
+ <dt>${escapeHtml17(row.label)}</dt>
7286
+ <dd>${escapeHtml17(row.value)}</dd>
5312
7287
  </div>`).join("")}</dl>
5313
7288
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
5314
- return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml13(model.status)}">
7289
+ return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml17(model.status)}">
5315
7290
  <header class="absolute-voice-provider-status__header">
5316
- <span class="absolute-voice-provider-status__eyebrow">${escapeHtml13(model.title)}</span>
5317
- <strong class="absolute-voice-provider-status__label">${escapeHtml13(model.label)}</strong>
7291
+ <span class="absolute-voice-provider-status__eyebrow">${escapeHtml17(model.title)}</span>
7292
+ <strong class="absolute-voice-provider-status__label">${escapeHtml17(model.label)}</strong>
5318
7293
  </header>
5319
- <p class="absolute-voice-provider-status__description">${escapeHtml13(model.description)}</p>
7294
+ <p class="absolute-voice-provider-status__description">${escapeHtml17(model.description)}</p>
5320
7295
  ${providers}
5321
- ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml13(model.error)}</p>` : ""}
7296
+ ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml17(model.error)}</p>` : ""}
5322
7297
  </section>`;
5323
7298
  };
5324
7299
  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}`;
@@ -5541,7 +7516,7 @@ var useVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
5541
7516
  // src/client/routingStatusWidget.ts
5542
7517
  var DEFAULT_TITLE12 = "Voice Routing";
5543
7518
  var DEFAULT_DESCRIPTION12 = "Latest provider routing decision from the self-hosted trace store.";
5544
- var escapeHtml14 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
7519
+ var escapeHtml18 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5545
7520
  var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
5546
7521
  var formatProviderRoutes2 = (routes) => routes && typeof routes === "object" ? Object.entries(routes).map(([role, provider]) => `${role}: ${formatValue(provider)}`).join(", ") || "None" : "None";
5547
7522
  var getProviderRoute = (routes, role) => routes && typeof routes === "object" ? formatValue(routes[role], "Not configured") : "Not configured";
@@ -5623,22 +7598,22 @@ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
5623
7598
  var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
5624
7599
  const model = createVoiceRoutingStatusViewModel(snapshot, options);
5625
7600
  const activeStack = model.activeStack.length ? `<div class="absolute-voice-routing-status__stack" aria-label="Active voice stack">${model.activeStack.map((item) => `<div>
5626
- <span>${escapeHtml14(item.label)}</span>
5627
- <strong>${escapeHtml14(item.value)}</strong>
7601
+ <span>${escapeHtml18(item.label)}</span>
7602
+ <strong>${escapeHtml18(item.value)}</strong>
5628
7603
  </div>`).join("")}</div>` : "";
5629
7604
  const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
5630
- <span>${escapeHtml14(row.label)}</span>
5631
- <strong>${escapeHtml14(row.value)}</strong>
7605
+ <span>${escapeHtml18(row.label)}</span>
7606
+ <strong>${escapeHtml18(row.value)}</strong>
5632
7607
  </div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
5633
- return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml14(model.status)}">
7608
+ return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml18(model.status)}">
5634
7609
  <header class="absolute-voice-routing-status__header">
5635
- <span class="absolute-voice-routing-status__eyebrow">${escapeHtml14(model.title)}</span>
5636
- <strong class="absolute-voice-routing-status__label">${escapeHtml14(model.label)}</strong>
7610
+ <span class="absolute-voice-routing-status__eyebrow">${escapeHtml18(model.title)}</span>
7611
+ <strong class="absolute-voice-routing-status__label">${escapeHtml18(model.label)}</strong>
5637
7612
  </header>
5638
- <p class="absolute-voice-routing-status__description">${escapeHtml14(model.description)}</p>
7613
+ <p class="absolute-voice-routing-status__description">${escapeHtml18(model.description)}</p>
5639
7614
  ${activeStack}
5640
7615
  ${rows}
5641
- ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml14(model.error)}</p>` : ""}
7616
+ ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml18(model.error)}</p>` : ""}
5642
7617
  </section>`;
5643
7618
  };
5644
7619
  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}}`;
@@ -5818,7 +7793,7 @@ var createVoiceTraceTimelineStore = (path = "/api/voice-traces", options = {}) =
5818
7793
  // src/client/traceTimelineWidget.ts
5819
7794
  var DEFAULT_TITLE13 = "Voice Traces";
5820
7795
  var DEFAULT_DESCRIPTION13 = "Latest call timelines with provider latency, fallbacks, handoffs, and errors from your self-hosted trace store.";
5821
- var escapeHtml15 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
7796
+ var escapeHtml19 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5822
7797
  var formatMs3 = (value) => typeof value === "number" ? `${value}ms` : "n/a";
5823
7798
  var formatProviders = (session) => session.providers.length ? session.providers.map((provider) => provider.provider).join(", ") : "No providers";
5824
7799
  var createVoiceTraceTimelineViewModel = (snapshot, options = {}) => {
@@ -5848,27 +7823,27 @@ var renderVoiceTraceTimelineWidgetHTML = (snapshot, options = {}) => {
5848
7823
  const model = createVoiceTraceTimelineViewModel(snapshot, options);
5849
7824
  const sessions = model.sessions.length ? `<div class="absolute-voice-trace-timeline__sessions">${model.sessions.map((session) => {
5850
7825
  const supportLinks = [
5851
- `<a href="${escapeHtml15(session.detailHref)}">Open timeline</a>`,
5852
- session.operationsRecordHref ? `<a href="${escapeHtml15(session.operationsRecordHref)}">Open operations record</a>` : undefined,
5853
- session.incidentBundleHref ? `<a href="${escapeHtml15(session.incidentBundleHref)}">Export incident bundle</a>` : undefined
7826
+ `<a href="${escapeHtml19(session.detailHref)}">Open timeline</a>`,
7827
+ session.operationsRecordHref ? `<a href="${escapeHtml19(session.operationsRecordHref)}">Open operations record</a>` : undefined,
7828
+ session.incidentBundleHref ? `<a href="${escapeHtml19(session.incidentBundleHref)}">Export incident bundle</a>` : undefined
5854
7829
  ].filter(Boolean).join("");
5855
- return `<article class="absolute-voice-trace-timeline__session absolute-voice-trace-timeline__session--${escapeHtml15(session.status)}">
7830
+ return `<article class="absolute-voice-trace-timeline__session absolute-voice-trace-timeline__session--${escapeHtml19(session.status)}">
5856
7831
  <header>
5857
- <strong>${escapeHtml15(session.sessionId)}</strong>
5858
- <span>${escapeHtml15(session.status)}</span>
7832
+ <strong>${escapeHtml19(session.sessionId)}</strong>
7833
+ <span>${escapeHtml19(session.status)}</span>
5859
7834
  </header>
5860
- <p>${escapeHtml15(session.label)} \xB7 ${escapeHtml15(session.durationLabel)} \xB7 ${escapeHtml15(session.providerLabel)}</p>
7835
+ <p>${escapeHtml19(session.label)} \xB7 ${escapeHtml19(session.durationLabel)} \xB7 ${escapeHtml19(session.providerLabel)}</p>
5861
7836
  <p class="absolute-voice-trace-timeline__actions">${supportLinks}</p>
5862
7837
  </article>`;
5863
7838
  }).join("")}</div>` : '<p class="absolute-voice-trace-timeline__empty">Run a voice session to see call timelines.</p>';
5864
- return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml15(model.status)}">
7839
+ return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml19(model.status)}">
5865
7840
  <header class="absolute-voice-trace-timeline__header">
5866
- <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml15(model.title)}</span>
5867
- <strong class="absolute-voice-trace-timeline__label">${escapeHtml15(model.label)}</strong>
7841
+ <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml19(model.title)}</span>
7842
+ <strong class="absolute-voice-trace-timeline__label">${escapeHtml19(model.label)}</strong>
5868
7843
  </header>
5869
- <p class="absolute-voice-trace-timeline__description">${escapeHtml15(model.description)}</p>
7844
+ <p class="absolute-voice-trace-timeline__description">${escapeHtml19(model.description)}</p>
5870
7845
  ${sessions}
5871
- ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml15(model.error)}</p>` : ""}
7846
+ ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml19(model.error)}</p>` : ""}
5872
7847
  </section>`;
5873
7848
  };
5874
7849
  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}`;
@@ -6027,8 +8002,8 @@ var VoiceTraceTimeline = ({
6027
8002
  import { useEffect as useEffect15, useRef as useRef15, useSyncExternalStore as useSyncExternalStore15 } from "react";
6028
8003
 
6029
8004
  // src/client/agentSquadStatus.ts
6030
- var getString = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
6031
- var getPayloadString = (event, key) => getString(event.payload?.[key]);
8005
+ var getString4 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
8006
+ var getPayloadString = (event, key) => getString4(event.payload?.[key]);
6032
8007
  var eventStatus = (event) => {
6033
8008
  const status = getPayloadString(event, "status");
6034
8009
  if (status === "blocked")
@@ -6120,7 +8095,7 @@ var useVoiceAgentSquadStatus = (path = "/api/voice-traces", options = {}) => {
6120
8095
  // src/client/agentSquadStatusWidget.ts
6121
8096
  var DEFAULT_TITLE14 = "Voice Agent Squad";
6122
8097
  var DEFAULT_DESCRIPTION14 = "Current specialist and recent handoffs from your self-hosted voice traces.";
6123
- var escapeHtml16 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
8098
+ var escapeHtml20 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6124
8099
  var labelFor = (current) => {
6125
8100
  if (!current)
6126
8101
  return "Waiting for specialist activity";
@@ -6147,24 +8122,24 @@ var renderVoiceAgentSquadStatusHTML = (snapshot, options = {}) => {
6147
8122
  const model = createVoiceAgentSquadStatusViewModel(snapshot, options);
6148
8123
  const current = model.current;
6149
8124
  const rows = model.sessions.length ? model.sessions.slice(0, 5).map((session) => `<li>
6150
- <span>${escapeHtml16(session.sessionId)}</span>
6151
- <strong>${escapeHtml16(session.targetAgentId ?? "none")}</strong>
6152
- <em>${escapeHtml16(session.status)}</em>
6153
- ${session.summary || session.reason ? `<p>${escapeHtml16(session.summary ?? session.reason ?? "")}</p>` : ""}
8125
+ <span>${escapeHtml20(session.sessionId)}</span>
8126
+ <strong>${escapeHtml20(session.targetAgentId ?? "none")}</strong>
8127
+ <em>${escapeHtml20(session.status)}</em>
8128
+ ${session.summary || session.reason ? `<p>${escapeHtml20(session.summary ?? session.reason ?? "")}</p>` : ""}
6154
8129
  </li>`).join("") : "<li><span>No squad traces yet.</span><strong>Waiting</strong></li>";
6155
8130
  return `<section class="absolute-voice-agent-squad-status">
6156
8131
  <header>
6157
- <span>${escapeHtml16(model.title)}</span>
6158
- <strong>${escapeHtml16(model.label)}</strong>
8132
+ <span>${escapeHtml20(model.title)}</span>
8133
+ <strong>${escapeHtml20(model.label)}</strong>
6159
8134
  </header>
6160
- <p>${escapeHtml16(model.description)}</p>
8135
+ <p>${escapeHtml20(model.description)}</p>
6161
8136
  <div>
6162
- <span>Session</span><strong>${escapeHtml16(current?.sessionId ?? "n/a")}</strong>
6163
- <span>From</span><strong>${escapeHtml16(current?.fromAgentId ?? "n/a")}</strong>
6164
- <span>Status</span><strong>${escapeHtml16(current?.status ?? "idle")}</strong>
8137
+ <span>Session</span><strong>${escapeHtml20(current?.sessionId ?? "n/a")}</strong>
8138
+ <span>From</span><strong>${escapeHtml20(current?.fromAgentId ?? "n/a")}</strong>
8139
+ <span>Status</span><strong>${escapeHtml20(current?.status ?? "idle")}</strong>
6165
8140
  </div>
6166
8141
  <ul>${rows}</ul>
6167
- ${model.error ? `<p class="absolute-voice-agent-squad-status__error">${escapeHtml16(model.error)}</p>` : ""}
8142
+ ${model.error ? `<p class="absolute-voice-agent-squad-status__error">${escapeHtml20(model.error)}</p>` : ""}
6168
8143
  </section>`;
6169
8144
  };
6170
8145
  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}`;
@@ -6401,7 +8376,7 @@ var useVoiceTurnLatency = (path = "/api/turn-latency", options = {}) => {
6401
8376
  var DEFAULT_TITLE15 = "Turn Latency";
6402
8377
  var DEFAULT_DESCRIPTION15 = "Per-turn timing from first transcript to commit and assistant response start.";
6403
8378
  var DEFAULT_PROOF_LABEL = "Run latency proof";
6404
- var escapeHtml17 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
8379
+ var escapeHtml21 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6405
8380
  var formatMs4 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
6406
8381
  var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
6407
8382
  const turns = (snapshot.report?.turns ?? []).map((turn) => ({
@@ -6429,25 +8404,25 @@ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
6429
8404
  };
6430
8405
  var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
6431
8406
  const model = createVoiceTurnLatencyViewModel(snapshot, options);
6432
- 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)}">
8407
+ 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)}">
6433
8408
  <header>
6434
- <strong>${escapeHtml17(turn.label)}</strong>
6435
- <span>${escapeHtml17(turn.status)}</span>
8409
+ <strong>${escapeHtml21(turn.label)}</strong>
8410
+ <span>${escapeHtml21(turn.status)}</span>
6436
8411
  </header>
6437
8412
  <dl>${turn.rows.map((row) => `<div>
6438
- <dt>${escapeHtml17(row.label)}</dt>
6439
- <dd>${escapeHtml17(row.value)}</dd>
8413
+ <dt>${escapeHtml21(row.label)}</dt>
8414
+ <dd>${escapeHtml21(row.value)}</dd>
6440
8415
  </div>`).join("")}</dl>
6441
8416
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-latency__empty">Complete a voice turn to see latency diagnostics.</p>';
6442
- return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml17(model.status)}">
8417
+ return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml21(model.status)}">
6443
8418
  <header class="absolute-voice-turn-latency__header">
6444
- <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml17(model.title)}</span>
6445
- <strong class="absolute-voice-turn-latency__label">${escapeHtml17(model.label)}</strong>
8419
+ <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml21(model.title)}</span>
8420
+ <strong class="absolute-voice-turn-latency__label">${escapeHtml21(model.label)}</strong>
6446
8421
  </header>
6447
- <p class="absolute-voice-turn-latency__description">${escapeHtml17(model.description)}</p>
6448
- ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml17(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
8422
+ <p class="absolute-voice-turn-latency__description">${escapeHtml21(model.description)}</p>
8423
+ ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml21(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
6449
8424
  ${turns}
6450
- ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml17(model.error)}</p>` : ""}
8425
+ ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml21(model.error)}</p>` : ""}
6451
8426
  </section>`;
6452
8427
  };
6453
8428
  var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {}) => {
@@ -6683,7 +8658,7 @@ var useVoiceTurnQuality = (path = "/api/turn-quality", options = {}) => {
6683
8658
  // src/client/turnQualityWidget.ts
6684
8659
  var DEFAULT_TITLE16 = "Turn Quality";
6685
8660
  var DEFAULT_DESCRIPTION16 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
6686
- var escapeHtml18 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
8661
+ var escapeHtml22 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6687
8662
  var formatConfidence = (value) => typeof value === "number" ? `${Math.round(value * 100)}%` : "n/a";
6688
8663
  var formatMaybe = (value) => value === undefined || value === "" ? "n/a" : String(value);
6689
8664
  var getTurnDetail = (turn) => {
@@ -6733,25 +8708,25 @@ var createVoiceTurnQualityViewModel = (snapshot, options = {}) => {
6733
8708
  };
6734
8709
  var renderVoiceTurnQualityHTML = (snapshot, options = {}) => {
6735
8710
  const model = createVoiceTurnQualityViewModel(snapshot, options);
6736
- 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)}">
8711
+ 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)}">
6737
8712
  <header>
6738
- <strong>${escapeHtml18(turn.label)}</strong>
6739
- <span>${escapeHtml18(turn.status)}</span>
8713
+ <strong>${escapeHtml22(turn.label)}</strong>
8714
+ <span>${escapeHtml22(turn.status)}</span>
6740
8715
  </header>
6741
- <p>${escapeHtml18(turn.detail)}</p>
8716
+ <p>${escapeHtml22(turn.detail)}</p>
6742
8717
  <dl>${turn.rows.map((row) => `<div>
6743
- <dt>${escapeHtml18(row.label)}</dt>
6744
- <dd>${escapeHtml18(row.value)}</dd>
8718
+ <dt>${escapeHtml22(row.label)}</dt>
8719
+ <dd>${escapeHtml22(row.value)}</dd>
6745
8720
  </div>`).join("")}</dl>
6746
8721
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-quality__empty">Complete a voice turn to see STT quality diagnostics.</p>';
6747
- return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml18(model.status)}">
8722
+ return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml22(model.status)}">
6748
8723
  <header class="absolute-voice-turn-quality__header">
6749
- <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml18(model.title)}</span>
6750
- <strong class="absolute-voice-turn-quality__label">${escapeHtml18(model.label)}</strong>
8724
+ <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml22(model.title)}</span>
8725
+ <strong class="absolute-voice-turn-quality__label">${escapeHtml22(model.label)}</strong>
6751
8726
  </header>
6752
- <p class="absolute-voice-turn-quality__description">${escapeHtml18(model.description)}</p>
8727
+ <p class="absolute-voice-turn-quality__description">${escapeHtml22(model.description)}</p>
6753
8728
  ${turns}
6754
- ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml18(model.error)}</p>` : ""}
8729
+ ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml22(model.error)}</p>` : ""}
6755
8730
  </section>`;
6756
8731
  };
6757
8732
  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}`;