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