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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  {
@@ -2194,6 +3947,228 @@ var appendRealCallRecoveryActionQuery = (href, query) => {
2194
3947
  const search = new URLSearchParams(entries).toString();
2195
3948
  return `${base}${separator}${search}${hash ? `#${hash}` : ""}`;
2196
3949
  };
3950
+ var sleepVoiceRealCallProfileRecoveryLoop = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3951
+ var describeVoiceRealCallProfileRecoveryLoopAction = (action) => [
3952
+ action.label ?? action.id ?? "recovery action",
3953
+ action.profileId ? `profile=${action.profileId}` : undefined,
3954
+ action.href
3955
+ ].filter(Boolean).join(" ");
3956
+ var defaultVoiceRealCallProfileRecoveryLoopActionFilter = (action, readinessCheckLabel) => action.method?.toUpperCase() === "POST" && action.sourceCheckLabel === readinessCheckLabel && typeof action.href === "string" && action.href.length > 0;
3957
+ var uniqueVoiceRealCallProfileRecoveryLoopActions = (actions) => {
3958
+ const seen = new Set;
3959
+ return actions.filter((action) => {
3960
+ const key = `${action.method?.toUpperCase() ?? "GET"} ${action.href ?? ""}`;
3961
+ if (seen.has(key)) {
3962
+ return false;
3963
+ }
3964
+ seen.add(key);
3965
+ return true;
3966
+ });
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
+ };
4060
+ var runVoiceRealCallProfileRecoveryLoop = async (options) => {
4061
+ const baseUrl = options.baseUrl.replace(/\/$/, "");
4062
+ const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
4063
+ const jobPollMs = options.jobPollMs ?? 1200;
4064
+ const jobTimeoutMs = options.jobTimeoutMs ?? 600000;
4065
+ const readinessCheckLabel = options.readinessCheckLabel ?? "Real-call profile history";
4066
+ const fetchImpl = options.fetch ?? fetch;
4067
+ const recoveryActionsHref = options.recoveryActionsHref ?? "/api/production-readiness/recovery-actions";
4068
+ const readinessHref = options.readinessHref ?? "/api/production-readiness";
4069
+ const refreshHref = options.refreshHref === undefined ? "/api/voice/real-call-profile-history/refresh" : options.refreshHref;
4070
+ const jobHref = options.jobHref ?? "/api/voice/real-call-profile-history/actions";
4071
+ const toAbsoluteUrl = (href) => new URL(href, baseUrl).toString();
4072
+ const parseJson = async (response) => {
4073
+ const text = await response.text();
4074
+ try {
4075
+ return JSON.parse(text);
4076
+ } catch (error) {
4077
+ throw new Error(`Expected JSON from ${response.url}, got: ${text.slice(0, 300)}`, { cause: error });
4078
+ }
4079
+ };
4080
+ const fetchJson = async (href, init) => {
4081
+ const response = await fetchImpl(toAbsoluteUrl(href), {
4082
+ headers: { accept: "application/json", ...init?.headers },
4083
+ ...init,
4084
+ signal: init?.signal ?? AbortSignal.timeout(requestTimeoutMs)
4085
+ });
4086
+ if (!response.ok) {
4087
+ throw new Error(`${href} returned HTTP ${String(response.status)}.`);
4088
+ }
4089
+ return parseJson(response);
4090
+ };
4091
+ const resolveJobHref = (jobId) => typeof jobHref === "function" ? jobHref(jobId) : `${jobHref.replace(/\/$/, "")}/${jobId}`;
4092
+ const getGate = async (fresh = false) => {
4093
+ const href = fresh ? `${readinessHref}${readinessHref.includes("?") ? "&" : "?"}voiceRecoveryLoopFresh=${String(Date.now())}` : readinessHref;
4094
+ const readiness = await fetchJson(href);
4095
+ return readiness.checks?.find((check) => check.label === readinessCheckLabel) ?? null;
4096
+ };
4097
+ const actionsResponse = await fetchJson(recoveryActionsHref);
4098
+ const actionFilter = options.actionFilter ?? ((action) => defaultVoiceRealCallProfileRecoveryLoopActionFilter(action, readinessCheckLabel));
4099
+ const actions = uniqueVoiceRealCallProfileRecoveryLoopActions((actionsResponse.actions ?? []).filter(actionFilter));
4100
+ if (actions.length === 0) {
4101
+ const realCallProfileGate2 = await getGate();
4102
+ return {
4103
+ actionCount: 0,
4104
+ actions,
4105
+ jobs: [],
4106
+ ok: realCallProfileGate2?.status === "pass",
4107
+ realCallProfileGate: realCallProfileGate2,
4108
+ startFailures: []
4109
+ };
4110
+ }
4111
+ options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel.`);
4112
+ for (const action of actions) {
4113
+ options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
4114
+ }
4115
+ const starts = await Promise.allSettled(actions.map(async (action) => {
4116
+ if (!action.href) {
4117
+ throw new Error("Recovery action is missing href.");
4118
+ }
4119
+ const body = await fetchJson(action.href, { method: "POST" });
4120
+ return { action, ...body };
4121
+ }));
4122
+ const startedJobs = starts.flatMap((result) => {
4123
+ if (result.status === "rejected") {
4124
+ return [];
4125
+ }
4126
+ return result.value.jobId ? [result.value] : [];
4127
+ });
4128
+ const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
4129
+ {
4130
+ action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
4131
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason)
4132
+ }
4133
+ ] : []);
4134
+ const pollJob = async (jobId) => {
4135
+ const deadline = Date.now() + jobTimeoutMs;
4136
+ while (Date.now() < deadline) {
4137
+ const body = await fetchJson(resolveJobHref(jobId));
4138
+ const job = body.job;
4139
+ if (!job) {
4140
+ throw new Error(`Recovery job ${jobId} was not found.`);
4141
+ }
4142
+ if (job.status === "pass" || job.status === "fail") {
4143
+ return job;
4144
+ }
4145
+ await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
4146
+ }
4147
+ throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
4148
+ };
4149
+ options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
4150
+ const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
4151
+ const jobs = jobResults.map((result, index) => ({
4152
+ action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
4153
+ jobId: startedJobs[index]?.jobId,
4154
+ result: result.status === "fulfilled" ? result.value : {
4155
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason),
4156
+ status: "fail"
4157
+ }
4158
+ }));
4159
+ if (refreshHref !== false) {
4160
+ await fetchJson(refreshHref, { method: "POST" });
4161
+ }
4162
+ const realCallProfileGate = await getGate(true);
4163
+ return {
4164
+ actionCount: actions.length,
4165
+ actions,
4166
+ jobs,
4167
+ ok: startFailures.length === 0 && jobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
4168
+ realCallProfileGate,
4169
+ startFailures
4170
+ };
4171
+ };
2197
4172
  var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
2198
4173
  const actions = [
2199
4174
  {
@@ -2910,7 +4885,7 @@ var buildVoiceProofTrendRecommendationReport = (report, options = {}) => {
2910
4885
  }
2911
4886
  };
2912
4887
  };
2913
- 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;");
2914
4889
  var escapeMarkdown = (value) => value.replaceAll("|", "\\|");
2915
4890
  var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provider Runtime Recommendations") => [
2916
4891
  `# ${title}`,
@@ -2943,11 +4918,11 @@ var renderVoiceProofTrendRecommendationMarkdown = (report, title = "Voice Provid
2943
4918
  ].join(`
2944
4919
  `);
2945
4920
  var renderVoiceProofTrendRecommendationHTML = (report, title = "Voice Provider Runtime Recommendations") => {
2946
- 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("");
2947
- const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml5(issue)}</li>`).join("");
2948
- 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("");
2949
- 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("");
2950
- 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>`;
2951
4926
  };
2952
4927
  var renderVoiceRealCallProfileHistoryMarkdown = (report, title = "Voice Real-Call Profile History") => [
2953
4928
  `# ${title}`,
@@ -2981,18 +4956,18 @@ var renderVoiceRealCallProfileHistoryMarkdown = (report, title = "Voice Real-Cal
2981
4956
  ].join(`
2982
4957
  `);
2983
4958
  var renderVoiceRealCallProfileHistoryHTML = (report, title = "Voice Real-Call Profile History") => {
2984
- 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>';
2985
- 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>';
2986
- 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("");
2987
- const issues = report.issues.length === 0 ? "<li>None</li>" : report.issues.map((issue) => `<li>${escapeHtml5(issue)}</li>`).join("");
2988
- 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>`;
2989
4964
  };
2990
4965
  var createVoiceProofTrendRecommendationRoutes = (options) => {
2991
4966
  const path = options.path ?? "/api/voice/proof-trend-recommendations";
2992
4967
  const htmlPath = options.htmlPath === undefined ? "/voice/proof-trend-recommendations" : options.htmlPath;
2993
4968
  const markdownPath = options.markdownPath === undefined ? "/voice/proof-trend-recommendations.md" : options.markdownPath;
2994
4969
  const title = options.title ?? "Voice Provider Runtime Recommendations";
2995
- const routes = new Elysia({
4970
+ const routes = new Elysia4({
2996
4971
  name: options.name ?? "absolutejs-voice-proof-trend-recommendations"
2997
4972
  });
2998
4973
  const loadReport = async () => {
@@ -3034,7 +5009,7 @@ var createVoiceRealCallProfileHistoryRoutes = (options = {}) => {
3034
5009
  const htmlPath = options.htmlPath === undefined ? "/voice/real-call-profile-history" : options.htmlPath;
3035
5010
  const markdownPath = options.markdownPath === undefined ? "/voice/real-call-profile-history.md" : options.markdownPath;
3036
5011
  const title = options.title ?? "Voice Real-Call Profile History";
3037
- const routes = new Elysia({
5012
+ const routes = new Elysia4({
3038
5013
  name: options.name ?? "absolutejs-voice-real-call-profile-history"
3039
5014
  });
3040
5015
  const loadReport = async () => {
@@ -3086,7 +5061,7 @@ var loadVoiceRealCallProfileHistoryRouteReport = async (options) => {
3086
5061
  };
3087
5062
  var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
3088
5063
  const path = options.path ?? "/api/voice/real-call-profile-history";
3089
- const routes = new Elysia({
5064
+ const routes = new Elysia4({
3090
5065
  name: options.name ?? "absolutejs-voice-real-call-profile-recovery-actions"
3091
5066
  });
3092
5067
  const actionPath = (actionId) => `${path}${realCallProfileActionPaths[actionId]}`;
@@ -3261,7 +5236,7 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
3261
5236
  };
3262
5237
  var createVoiceProofTrendRoutes = (options) => {
3263
5238
  const path = options.path ?? "/api/voice/proof-trends";
3264
- const routes = new Elysia({
5239
+ const routes = new Elysia4({
3265
5240
  name: options.name ?? "absolutejs-voice-proof-trends"
3266
5241
  });
3267
5242
  routes.get(path, async () => {
@@ -3380,7 +5355,7 @@ var DEFAULT_LINKS2 = [
3380
5355
  { href: "/voice/proof-trends", label: "Trend page" },
3381
5356
  { href: "/api/voice/proof-trends", label: "Trend JSON" }
3382
5357
  ];
3383
- var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5358
+ var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3384
5359
  var formatMs = (value) => typeof value === "number" && Number.isFinite(value) ? `${Math.round(value)}ms` : "n/a";
3385
5360
  var statusLabel = (report) => {
3386
5361
  if (!report) {
@@ -3430,19 +5405,19 @@ var createVoiceProofTrendsViewModel = (snapshot, options = {}) => {
3430
5405
  var renderVoiceProofTrendsHTML = (snapshot, options = {}) => {
3431
5406
  const model = createVoiceProofTrendsViewModel(snapshot, options);
3432
5407
  const metrics = model.metrics.length ? `<div class="absolute-voice-proof-trends__metrics">${model.metrics.map((metric) => `<article>
3433
- <span>${escapeHtml6(metric.label)}</span>
3434
- <strong>${escapeHtml6(metric.value)}</strong>
3435
- </article>`).join("")}</div>` : `<p class="absolute-voice-proof-trends__empty">${model.error ? escapeHtml6(model.error) : "Run the sustained proof trends script to populate evidence."}</p>`;
3436
- const links = model.links.length ? `<p class="absolute-voice-proof-trends__links">${model.links.map((link) => `<a href="${escapeHtml6(link.href)}">${escapeHtml6(link.label)}</a>`).join("")}</p>` : "";
3437
- return `<section class="absolute-voice-proof-trends absolute-voice-proof-trends--${escapeHtml6(model.status)}">
5408
+ <span>${escapeHtml10(metric.label)}</span>
5409
+ <strong>${escapeHtml10(metric.value)}</strong>
5410
+ </article>`).join("")}</div>` : `<p class="absolute-voice-proof-trends__empty">${model.error ? escapeHtml10(model.error) : "Run the sustained proof trends script to populate evidence."}</p>`;
5411
+ const links = model.links.length ? `<p class="absolute-voice-proof-trends__links">${model.links.map((link) => `<a href="${escapeHtml10(link.href)}">${escapeHtml10(link.label)}</a>`).join("")}</p>` : "";
5412
+ return `<section class="absolute-voice-proof-trends absolute-voice-proof-trends--${escapeHtml10(model.status)}">
3438
5413
  <header class="absolute-voice-proof-trends__header">
3439
- <span class="absolute-voice-proof-trends__eyebrow">${escapeHtml6(model.title)}</span>
3440
- <strong class="absolute-voice-proof-trends__label">${escapeHtml6(model.label)}</strong>
5414
+ <span class="absolute-voice-proof-trends__eyebrow">${escapeHtml10(model.title)}</span>
5415
+ <strong class="absolute-voice-proof-trends__label">${escapeHtml10(model.label)}</strong>
3441
5416
  </header>
3442
- <p class="absolute-voice-proof-trends__description">${escapeHtml6(model.description)}</p>
5417
+ <p class="absolute-voice-proof-trends__description">${escapeHtml10(model.description)}</p>
3443
5418
  ${metrics}
3444
5419
  ${links}
3445
- ${model.error ? `<p class="absolute-voice-proof-trends__error">${escapeHtml6(model.error)}</p>` : ""}
5420
+ ${model.error ? `<p class="absolute-voice-proof-trends__error">${escapeHtml10(model.error)}</p>` : ""}
3446
5421
  </section>`;
3447
5422
  };
3448
5423
  var getVoiceProofTrendsCSS = () => `.absolute-voice-proof-trends{border:1px solid #99f6e4;border-radius:20px;background:#f0fdfa;color:#0f172a;padding:18px;box-shadow:0 18px 40px rgba(13,148,136,.12);font-family:inherit}.absolute-voice-proof-trends--warning,.absolute-voice-proof-trends--error{border-color:#f2a7a7;background:#fff7f4}.absolute-voice-proof-trends__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-proof-trends__eyebrow{color:#0f766e;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-proof-trends__label{font-size:24px;line-height:1}.absolute-voice-proof-trends__description,.absolute-voice-proof-trends__empty{color:#475569}.absolute-voice-proof-trends__metrics{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));margin-top:14px}.absolute-voice-proof-trends__metrics article{background:#fff;border:1px solid #ccfbf1;border-radius:16px;padding:12px}.absolute-voice-proof-trends__metrics span{color:#64748b;display:block;font-size:12px;font-weight:800;text-transform:uppercase}.absolute-voice-proof-trends__metrics strong{display:block;font-size:20px;margin-top:4px}.absolute-voice-proof-trends__links{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0 0}.absolute-voice-proof-trends__links a{border:1px solid #99f6e4;border-radius:999px;color:#0f766e;font-weight:800;padding:6px 10px;text-decoration:none}.absolute-voice-proof-trends__error{color:#9f1239;font-weight:700}`;
@@ -3650,7 +5625,7 @@ var DEFAULT_LINKS3 = [
3650
5625
  { href: "/production-readiness", label: "Readiness page" },
3651
5626
  { href: "/voice/slo-readiness-thresholds", label: "Gate thresholds" }
3652
5627
  ];
3653
- 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;");
3654
5629
  var formatExplanationValue = (value, unit) => {
3655
5630
  if (value === undefined || value === null) {
3656
5631
  return "n/a";
@@ -3691,23 +5666,23 @@ var createVoiceReadinessFailuresViewModel = (snapshot, options = {}) => {
3691
5666
  };
3692
5667
  var renderVoiceReadinessFailuresHTML = (snapshot, options = {}) => {
3693
5668
  const model = createVoiceReadinessFailuresViewModel(snapshot, options);
3694
- 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)}">
3695
- <span>${escapeHtml7(failure.status.toUpperCase())}</span>
3696
- <strong>${escapeHtml7(failure.label)}</strong>
3697
- <p>Observed ${escapeHtml7(failure.observed)} against ${escapeHtml7(failure.thresholdLabel)} ${escapeHtml7(failure.threshold)}.</p>
3698
- <p>${escapeHtml7(failure.remediation)}</p>
3699
- <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>
3700
- </article>`).join("")}</div>` : `<p class="absolute-voice-readiness-failures__empty">${model.error ? escapeHtml7(model.error) : "No calibrated readiness gate explanations are open."}</p>`;
3701
- 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>` : "";
3702
- 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)}">
3703
5678
  <header class="absolute-voice-readiness-failures__header">
3704
- <span class="absolute-voice-readiness-failures__eyebrow">${escapeHtml7(model.title)}</span>
3705
- <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>
3706
5681
  </header>
3707
- <p class="absolute-voice-readiness-failures__description">${escapeHtml7(model.description)}</p>
5682
+ <p class="absolute-voice-readiness-failures__description">${escapeHtml11(model.description)}</p>
3708
5683
  ${failures}
3709
5684
  ${links}
3710
- ${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>` : ""}
3711
5686
  </section>`;
3712
5687
  };
3713
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}`;
@@ -3924,7 +5899,7 @@ var createVoiceProviderSimulationControlsStore = (options) => {
3924
5899
  };
3925
5900
 
3926
5901
  // src/client/providerSimulationControlsWidget.ts
3927
- 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;");
3928
5903
  var formatKind = (kind) => (kind ?? "stt").toUpperCase();
3929
5904
  var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
3930
5905
  const configuredProviders = options.providers.filter((provider) => provider.configured !== false);
@@ -3944,18 +5919,18 @@ var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
3944
5919
  };
3945
5920
  var renderVoiceProviderSimulationControlsHTML = (snapshot, options) => {
3946
5921
  const model = createVoiceProviderSimulationControlsViewModel(snapshot, options);
3947
- 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("");
3948
- 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("");
3949
5924
  return `<section class="absolute-voice-provider-simulation absolute-voice-provider-simulation--${snapshot.error ? "error" : snapshot.isRunning ? "running" : "ready"}">
3950
5925
  <header class="absolute-voice-provider-simulation__header">
3951
- <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml8(model.title)}</span>
3952
- <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>
3953
5928
  </header>
3954
- <p class="absolute-voice-provider-simulation__description">${escapeHtml8(model.description)}</p>
3955
- ${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>`}
3956
5931
  <div class="absolute-voice-provider-simulation__actions">${failureButtons}${recoveryButtons}</div>
3957
- ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml8(snapshot.error)}</p>` : ""}
3958
- ${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>` : ""}
3959
5934
  </section>`;
3960
5935
  };
3961
5936
  var bindVoiceProviderSimulationControls = (element, store) => {
@@ -4216,7 +6191,7 @@ var createVoiceProviderCapabilitiesStore = (path = "/api/provider-capabilities",
4216
6191
  // src/client/providerCapabilitiesWidget.ts
4217
6192
  var DEFAULT_TITLE7 = "Provider Capabilities";
4218
6193
  var DEFAULT_DESCRIPTION7 = "Configured, selected, and healthy voice providers for this deployment.";
4219
- 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;");
4220
6195
  var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
4221
6196
  var formatKind2 = (kind) => kind.toUpperCase();
4222
6197
  var formatStatus2 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
@@ -4271,25 +6246,25 @@ var createVoiceProviderCapabilitiesViewModel = (snapshot, options = {}) => {
4271
6246
  };
4272
6247
  var renderVoiceProviderCapabilitiesHTML = (snapshot, options = {}) => {
4273
6248
  const model = createVoiceProviderCapabilitiesViewModel(snapshot, options);
4274
- 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)}">
4275
6250
  <header>
4276
- <strong>${escapeHtml9(capability.label)}</strong>
4277
- <span>${escapeHtml9(formatStatus2(capability.status))}</span>
6251
+ <strong>${escapeHtml13(capability.label)}</strong>
6252
+ <span>${escapeHtml13(formatStatus2(capability.status))}</span>
4278
6253
  </header>
4279
- <p>${escapeHtml9(capability.detail)}</p>
6254
+ <p>${escapeHtml13(capability.detail)}</p>
4280
6255
  <dl>${capability.rows.map((row) => `<div>
4281
- <dt>${escapeHtml9(row.label)}</dt>
4282
- <dd>${escapeHtml9(row.value)}</dd>
6256
+ <dt>${escapeHtml13(row.label)}</dt>
6257
+ <dd>${escapeHtml13(row.value)}</dd>
4283
6258
  </div>`).join("")}</dl>
4284
6259
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-capabilities__empty">Configure provider capabilities to see deployment coverage.</p>';
4285
- 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)}">
4286
6261
  <header class="absolute-voice-provider-capabilities__header">
4287
- <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml9(model.title)}</span>
4288
- <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>
4289
6264
  </header>
4290
- <p class="absolute-voice-provider-capabilities__description">${escapeHtml9(model.description)}</p>
6265
+ <p class="absolute-voice-provider-capabilities__description">${escapeHtml13(model.description)}</p>
4291
6266
  ${capabilities}
4292
- ${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>` : ""}
4293
6268
  </section>`;
4294
6269
  };
4295
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}`;
@@ -4546,7 +6521,7 @@ function useVoiceProviderContracts(path = "/api/provider-contracts", options = {
4546
6521
  // src/client/providerContractsWidget.ts
4547
6522
  var DEFAULT_TITLE8 = "Provider Contracts";
4548
6523
  var DEFAULT_DESCRIPTION8 = "Production contract coverage for provider env, latency, fallback, streaming, and capabilities.";
4549
- 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;");
4550
6525
  var formatProvider2 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
4551
6526
  var formatStatus3 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
4552
6527
  var contractDetail = (row) => {
@@ -4590,26 +6565,26 @@ var createVoiceProviderContractsViewModel = (snapshot, options = {}) => {
4590
6565
  };
4591
6566
  var renderVoiceProviderContractsHTML = (snapshot, options = {}) => {
4592
6567
  const model = createVoiceProviderContractsViewModel(snapshot, options);
4593
- 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)}">
4594
6569
  <header>
4595
- <strong>${escapeHtml10(row.label)}</strong>
4596
- <span>${escapeHtml10(formatStatus3(row.status))}</span>
6570
+ <strong>${escapeHtml14(row.label)}</strong>
6571
+ <span>${escapeHtml14(formatStatus3(row.status))}</span>
4597
6572
  </header>
4598
- <p>${escapeHtml10(row.detail)}</p>
4599
- ${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>` : ""}
4600
6575
  <dl>${row.rows.map((item) => `<div>
4601
- <dt>${escapeHtml10(item.label)}</dt>
4602
- <dd>${escapeHtml10(item.value)}</dd>
6576
+ <dt>${escapeHtml14(item.label)}</dt>
6577
+ <dd>${escapeHtml14(item.value)}</dd>
4603
6578
  </div>`).join("")}</dl>
4604
6579
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-contracts__empty">Configure provider contracts to see production coverage.</p>';
4605
- 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)}">
4606
6581
  <header class="absolute-voice-provider-contracts__header">
4607
- <span class="absolute-voice-provider-contracts__eyebrow">${escapeHtml10(model.title)}</span>
4608
- <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>
4609
6584
  </header>
4610
- <p class="absolute-voice-provider-contracts__description">${escapeHtml10(model.description)}</p>
6585
+ <p class="absolute-voice-provider-contracts__description">${escapeHtml14(model.description)}</p>
4611
6586
  ${rows}
4612
- ${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>` : ""}
4613
6588
  </section>`;
4614
6589
  };
4615
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}`;
@@ -4806,7 +6781,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
4806
6781
  // src/client/providerStatusWidget.ts
4807
6782
  var DEFAULT_TITLE9 = "Voice Providers";
4808
6783
  var DEFAULT_DESCRIPTION9 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
4809
- 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;");
4810
6785
  var formatProvider3 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
4811
6786
  var formatStatus4 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
4812
6787
  var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
@@ -4862,25 +6837,25 @@ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
4862
6837
  };
4863
6838
  var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
4864
6839
  const model = createVoiceProviderStatusViewModel(snapshot, options);
4865
- 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)}">
4866
6841
  <header>
4867
- <strong>${escapeHtml11(provider.label)}</strong>
4868
- <span>${escapeHtml11(formatStatus4(provider.status))}</span>
6842
+ <strong>${escapeHtml15(provider.label)}</strong>
6843
+ <span>${escapeHtml15(formatStatus4(provider.status))}</span>
4869
6844
  </header>
4870
- <p>${escapeHtml11(provider.detail)}</p>
6845
+ <p>${escapeHtml15(provider.detail)}</p>
4871
6846
  <dl>${provider.rows.map((row) => `<div>
4872
- <dt>${escapeHtml11(row.label)}</dt>
4873
- <dd>${escapeHtml11(row.value)}</dd>
6847
+ <dt>${escapeHtml15(row.label)}</dt>
6848
+ <dd>${escapeHtml15(row.value)}</dd>
4874
6849
  </div>`).join("")}</dl>
4875
6850
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
4876
- 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)}">
4877
6852
  <header class="absolute-voice-provider-status__header">
4878
- <span class="absolute-voice-provider-status__eyebrow">${escapeHtml11(model.title)}</span>
4879
- <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>
4880
6855
  </header>
4881
- <p class="absolute-voice-provider-status__description">${escapeHtml11(model.description)}</p>
6856
+ <p class="absolute-voice-provider-status__description">${escapeHtml15(model.description)}</p>
4882
6857
  ${providers}
4883
- ${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>` : ""}
4884
6859
  </section>`;
4885
6860
  };
4886
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}`;
@@ -5109,7 +7084,7 @@ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {})
5109
7084
  // src/client/routingStatusWidget.ts
5110
7085
  var DEFAULT_TITLE10 = "Voice Routing";
5111
7086
  var DEFAULT_DESCRIPTION10 = "Latest provider routing decision from the self-hosted trace store.";
5112
- 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;");
5113
7088
  var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
5114
7089
  var formatProviderRoutes = (routes) => routes && typeof routes === "object" ? Object.entries(routes).map(([role, provider]) => `${role}: ${formatValue(provider)}`).join(", ") || "None" : "None";
5115
7090
  var getProviderRoute = (routes, role) => routes && typeof routes === "object" ? formatValue(routes[role], "Not configured") : "Not configured";
@@ -5191,22 +7166,22 @@ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
5191
7166
  var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
5192
7167
  const model = createVoiceRoutingStatusViewModel(snapshot, options);
5193
7168
  const activeStack = model.activeStack.length ? `<div class="absolute-voice-routing-status__stack" aria-label="Active voice stack">${model.activeStack.map((item) => `<div>
5194
- <span>${escapeHtml12(item.label)}</span>
5195
- <strong>${escapeHtml12(item.value)}</strong>
7169
+ <span>${escapeHtml16(item.label)}</span>
7170
+ <strong>${escapeHtml16(item.value)}</strong>
5196
7171
  </div>`).join("")}</div>` : "";
5197
7172
  const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
5198
- <span>${escapeHtml12(row.label)}</span>
5199
- <strong>${escapeHtml12(row.value)}</strong>
7173
+ <span>${escapeHtml16(row.label)}</span>
7174
+ <strong>${escapeHtml16(row.value)}</strong>
5200
7175
  </div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
5201
- 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)}">
5202
7177
  <header class="absolute-voice-routing-status__header">
5203
- <span class="absolute-voice-routing-status__eyebrow">${escapeHtml12(model.title)}</span>
5204
- <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>
5205
7180
  </header>
5206
- <p class="absolute-voice-routing-status__description">${escapeHtml12(model.description)}</p>
7181
+ <p class="absolute-voice-routing-status__description">${escapeHtml16(model.description)}</p>
5207
7182
  ${activeStack}
5208
7183
  ${rows}
5209
- ${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>` : ""}
5210
7185
  </section>`;
5211
7186
  };
5212
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}}`;
@@ -5420,8 +7395,8 @@ var createVoiceTraceTimelineStore = (path = "/api/voice-traces", options = {}) =
5420
7395
  };
5421
7396
 
5422
7397
  // src/client/agentSquadStatus.ts
5423
- var getString = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
5424
- 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]);
5425
7400
  var eventStatus = (event) => {
5426
7401
  const status = getPayloadString(event, "status");
5427
7402
  if (status === "blocked")
@@ -5637,7 +7612,7 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
5637
7612
  var DEFAULT_TITLE11 = "Turn Latency";
5638
7613
  var DEFAULT_DESCRIPTION11 = "Per-turn timing from first transcript to commit and assistant response start.";
5639
7614
  var DEFAULT_PROOF_LABEL = "Run latency proof";
5640
- 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;");
5641
7616
  var formatMs2 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
5642
7617
  var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
5643
7618
  const turns = (snapshot.report?.turns ?? []).map((turn) => ({
@@ -5665,25 +7640,25 @@ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
5665
7640
  };
5666
7641
  var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
5667
7642
  const model = createVoiceTurnLatencyViewModel(snapshot, options);
5668
- 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)}">
5669
7644
  <header>
5670
- <strong>${escapeHtml13(turn.label)}</strong>
5671
- <span>${escapeHtml13(turn.status)}</span>
7645
+ <strong>${escapeHtml17(turn.label)}</strong>
7646
+ <span>${escapeHtml17(turn.status)}</span>
5672
7647
  </header>
5673
7648
  <dl>${turn.rows.map((row) => `<div>
5674
- <dt>${escapeHtml13(row.label)}</dt>
5675
- <dd>${escapeHtml13(row.value)}</dd>
7649
+ <dt>${escapeHtml17(row.label)}</dt>
7650
+ <dd>${escapeHtml17(row.value)}</dd>
5676
7651
  </div>`).join("")}</dl>
5677
7652
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-latency__empty">Complete a voice turn to see latency diagnostics.</p>';
5678
- 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)}">
5679
7654
  <header class="absolute-voice-turn-latency__header">
5680
- <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml13(model.title)}</span>
5681
- <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>
5682
7657
  </header>
5683
- <p class="absolute-voice-turn-latency__description">${escapeHtml13(model.description)}</p>
5684
- ${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>` : ""}
5685
7660
  ${turns}
5686
- ${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>` : ""}
5687
7662
  </section>`;
5688
7663
  };
5689
7664
  var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {}) => {
@@ -5916,7 +7891,7 @@ var createVoiceTurnQualityStore = (path = "/api/turn-quality", options = {}) =>
5916
7891
  // src/client/turnQualityWidget.ts
5917
7892
  var DEFAULT_TITLE12 = "Turn Quality";
5918
7893
  var DEFAULT_DESCRIPTION12 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
5919
- 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;");
5920
7895
  var formatConfidence = (value) => typeof value === "number" ? `${Math.round(value * 100)}%` : "n/a";
5921
7896
  var formatMaybe = (value) => value === undefined || value === "" ? "n/a" : String(value);
5922
7897
  var getTurnDetail = (turn) => {
@@ -5966,25 +7941,25 @@ var createVoiceTurnQualityViewModel = (snapshot, options = {}) => {
5966
7941
  };
5967
7942
  var renderVoiceTurnQualityHTML = (snapshot, options = {}) => {
5968
7943
  const model = createVoiceTurnQualityViewModel(snapshot, options);
5969
- 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)}">
5970
7945
  <header>
5971
- <strong>${escapeHtml14(turn.label)}</strong>
5972
- <span>${escapeHtml14(turn.status)}</span>
7946
+ <strong>${escapeHtml18(turn.label)}</strong>
7947
+ <span>${escapeHtml18(turn.status)}</span>
5973
7948
  </header>
5974
- <p>${escapeHtml14(turn.detail)}</p>
7949
+ <p>${escapeHtml18(turn.detail)}</p>
5975
7950
  <dl>${turn.rows.map((row) => `<div>
5976
- <dt>${escapeHtml14(row.label)}</dt>
5977
- <dd>${escapeHtml14(row.value)}</dd>
7951
+ <dt>${escapeHtml18(row.label)}</dt>
7952
+ <dd>${escapeHtml18(row.value)}</dd>
5978
7953
  </div>`).join("")}</dl>
5979
7954
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-quality__empty">Complete a voice turn to see STT quality diagnostics.</p>';
5980
- 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)}">
5981
7956
  <header class="absolute-voice-turn-quality__header">
5982
- <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml14(model.title)}</span>
5983
- <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>
5984
7959
  </header>
5985
- <p class="absolute-voice-turn-quality__description">${escapeHtml14(model.description)}</p>
7960
+ <p class="absolute-voice-turn-quality__description">${escapeHtml18(model.description)}</p>
5986
7961
  ${turns}
5987
- ${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>` : ""}
5988
7963
  </section>`;
5989
7964
  };
5990
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}`;