@absolutejs/voice 0.0.22-beta.155 → 0.0.22-beta.157

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/README.md CHANGED
@@ -703,6 +703,8 @@ app.use(
703
703
  );
704
704
  ```
705
705
 
706
+ The same route exposes `GET /api/voice/ops-actions/history` and `/voice/ops-actions` so apps can show recent operator actions beside the action center. For HTML or HTMX pages, use `mountVoiceOpsActionHistory(...)` from `@absolutejs/voice/client`.
707
+
706
708
  For HTML or HTMX pages:
707
709
 
708
710
  ```html
@@ -9,9 +9,11 @@ export { createVoiceBargeInMonitor } from './bargeInMonitor';
9
9
  export { createVoiceLiveTurnLatencyMonitor } from './liveTurnLatency';
10
10
  export { createVoiceOpsStatusStore, fetchVoiceOpsStatus } from './opsStatus';
11
11
  export { createVoiceOpsActionCenterActions, createVoiceOpsActionCenterStore, recordVoiceOpsActionResult, runVoiceOpsAction } from './opsActionCenter';
12
+ export { createVoiceOpsActionHistoryStore, fetchVoiceOpsActionHistory } from './opsActionHistory';
12
13
  export { createVoiceDeliveryRuntimeStore, fetchVoiceDeliveryRuntime, runVoiceDeliveryRuntimeAction } from './deliveryRuntime';
13
14
  export { createVoiceOpsStatusViewModel, defineVoiceOpsStatusElement, getVoiceOpsStatusCSS, getVoiceOpsStatusLabel, mountVoiceOpsStatus, renderVoiceOpsStatusHTML } from './opsStatusWidget';
14
15
  export { createVoiceOpsActionCenterViewModel, defineVoiceOpsActionCenterElement, getVoiceOpsActionCenterCSS, mountVoiceOpsActionCenter, renderVoiceOpsActionCenterHTML } from './opsActionCenterWidget';
16
+ export { getVoiceOpsActionHistoryCSS, mountVoiceOpsActionHistory, renderVoiceOpsActionHistoryWidgetHTML } from './opsActionHistoryWidget';
15
17
  export { createVoiceDeliveryRuntimeViewModel, defineVoiceDeliveryRuntimeElement, getVoiceDeliveryRuntimeCSS, mountVoiceDeliveryRuntime, renderVoiceDeliveryRuntimeHTML } from './deliveryRuntimeWidget';
16
18
  export { createVoiceRoutingStatusStore, fetchVoiceRoutingStatus } from './routingStatus';
17
19
  export { createVoiceRoutingStatusViewModel, defineVoiceRoutingStatusElement, getVoiceRoutingStatusCSS, mountVoiceRoutingStatus, renderVoiceRoutingStatusHTML } from './routingStatusWidget';
@@ -31,11 +33,13 @@ export { createVoiceTraceTimelineViewModel, defineVoiceTraceTimelineElement, get
31
33
  export { createVoiceWorkflowStatusStore, fetchVoiceWorkflowStatus } from './workflowStatus';
32
34
  export type { VoiceOpsStatusClientOptions, VoiceOpsStatusSnapshot } from './opsStatus';
33
35
  export type { VoiceOpsActionCenterClientOptions, VoiceOpsActionCenterPresetOptions, VoiceOpsActionCenterSnapshot, VoiceOpsActionDescriptor, VoiceOpsActionMethod, VoiceOpsActionRunResult } from './opsActionCenter';
36
+ export type { VoiceOpsActionHistoryClientOptions, VoiceOpsActionHistorySnapshot } from './opsActionHistory';
34
37
  export type { VoiceDeliveryRuntimeClientOptions, VoiceDeliveryRuntimeAction, VoiceDeliveryRuntimeActionResult, VoiceDeliveryRuntimeSnapshot } from './deliveryRuntime';
35
38
  export type { VoiceBargeInMonitorOptions } from './bargeInMonitor';
36
39
  export type { VoiceLiveTurnLatencyEvent, VoiceLiveTurnLatencyMonitorOptions, VoiceLiveTurnLatencySnapshot, VoiceLiveTurnLatencyStatus } from './liveTurnLatency';
37
40
  export type { VoiceOpsStatusSurfaceView, VoiceOpsStatusViewModel, VoiceOpsStatusWidgetOptions } from './opsStatusWidget';
38
41
  export type { VoiceOpsActionCenterViewModel, VoiceOpsActionCenterWidgetOptions } from './opsActionCenterWidget';
42
+ export type { VoiceOpsActionHistoryWidgetOptions } from './opsActionHistoryWidget';
39
43
  export type { VoiceDeliveryRuntimeSurfaceView, VoiceDeliveryRuntimeViewModel, VoiceDeliveryRuntimeWidgetOptions } from './deliveryRuntimeWidget';
40
44
  export type { VoiceRoutingStatusClientOptions, VoiceRoutingStatusSnapshot } from './routingStatus';
41
45
  export type { VoiceRoutingStatusViewModel, VoiceRoutingStatusWidgetOptions } from './routingStatusWidget';
@@ -2252,6 +2252,80 @@ var createVoiceOpsActionCenterStore = (options = {}) => {
2252
2252
  }
2253
2253
  };
2254
2254
  };
2255
+ // src/client/opsActionHistory.ts
2256
+ var fetchVoiceOpsActionHistory = async (path = "/api/voice/ops-actions/history", options = {}) => {
2257
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2258
+ const response = await fetchImpl(path);
2259
+ if (!response.ok) {
2260
+ throw new Error(`Voice ops action history failed: HTTP ${response.status}`);
2261
+ }
2262
+ return await response.json();
2263
+ };
2264
+ var createVoiceOpsActionHistoryStore = (path = "/api/voice/ops-actions/history", options = {}) => {
2265
+ const listeners = new Set;
2266
+ let closed = false;
2267
+ let timer;
2268
+ let snapshot = {
2269
+ error: null,
2270
+ isLoading: false
2271
+ };
2272
+ const emit = () => {
2273
+ for (const listener of listeners) {
2274
+ listener();
2275
+ }
2276
+ };
2277
+ const refresh = async () => {
2278
+ if (closed) {
2279
+ return snapshot.report;
2280
+ }
2281
+ snapshot = { ...snapshot, error: null, isLoading: true };
2282
+ emit();
2283
+ try {
2284
+ const report = await fetchVoiceOpsActionHistory(path, options);
2285
+ snapshot = {
2286
+ error: null,
2287
+ isLoading: false,
2288
+ report,
2289
+ updatedAt: Date.now()
2290
+ };
2291
+ emit();
2292
+ return report;
2293
+ } catch (error) {
2294
+ snapshot = {
2295
+ ...snapshot,
2296
+ error: error instanceof Error ? error.message : String(error),
2297
+ isLoading: false
2298
+ };
2299
+ emit();
2300
+ throw error;
2301
+ }
2302
+ };
2303
+ const close = () => {
2304
+ closed = true;
2305
+ if (timer) {
2306
+ clearInterval(timer);
2307
+ timer = undefined;
2308
+ }
2309
+ listeners.clear();
2310
+ };
2311
+ if (options.intervalMs && options.intervalMs > 0) {
2312
+ timer = setInterval(() => {
2313
+ refresh().catch(() => {});
2314
+ }, options.intervalMs);
2315
+ }
2316
+ return {
2317
+ close,
2318
+ getServerSnapshot: () => snapshot,
2319
+ getSnapshot: () => snapshot,
2320
+ refresh,
2321
+ subscribe: (listener) => {
2322
+ listeners.add(listener);
2323
+ return () => {
2324
+ listeners.delete(listener);
2325
+ };
2326
+ }
2327
+ };
2328
+ };
2255
2329
  // src/client/deliveryRuntime.ts
2256
2330
  var getDefaultActionPath = (path, action, options) => {
2257
2331
  if (action === "tick") {
@@ -2599,10 +2673,40 @@ var defineVoiceOpsActionCenterElement = (tagName = "absolute-voice-ops-action-ce
2599
2673
  }
2600
2674
  });
2601
2675
  };
2676
+ // src/client/opsActionHistoryWidget.ts
2677
+ var escapeHtml3 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2678
+ var renderVoiceOpsActionHistoryWidgetHTML = (snapshot, options = {}) => {
2679
+ const report = snapshot.report;
2680
+ const entries = (report?.entries ?? []).slice(0, options.limit ?? 5);
2681
+ const rows = entries.map((entry) => `<li class="absolute-voice-ops-action-history__entry absolute-voice-ops-action-history__entry--${entry.ok ? "success" : "error"}"><span>${escapeHtml3(entry.actionId)}</span><strong>${escapeHtml3(entry.ok ? "Success" : "Failed")}</strong><small>${escapeHtml3(new Date(entry.at).toLocaleString())}${entry.status ? ` \xB7 HTTP ${String(entry.status)}` : ""}</small></li>`).join("");
2682
+ return `<section class="absolute-voice-ops-action-history">
2683
+ <header><span>Operator proof</span><strong>${escapeHtml3(options.title ?? "Action History")}</strong></header>
2684
+ <p>${String(report?.total ?? 0)} action(s), ${String(report?.failed ?? 0)} failed.</p>
2685
+ <ul>${rows || "<li>No operator actions recorded yet.</li>"}</ul>
2686
+ ${snapshot.error ? `<p class="absolute-voice-ops-action-history__error">${escapeHtml3(snapshot.error)}</p>` : ""}
2687
+ </section>`;
2688
+ };
2689
+ var getVoiceOpsActionHistoryCSS = () => `.absolute-voice-ops-action-history{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;font-family:inherit}.absolute-voice-ops-action-history header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-ops-action-history header span{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-ops-action-history header strong{font-size:24px}.absolute-voice-ops-action-history p{color:#514733}.absolute-voice-ops-action-history ul{display:grid;gap:8px;list-style:none;margin:12px 0 0;padding:0}.absolute-voice-ops-action-history__entry{background:#fff;border:1px solid #eee4d2;border-radius:14px;display:grid;gap:3px;padding:10px 12px}.absolute-voice-ops-action-history__entry--error{border-color:#f2a7a7}.absolute-voice-ops-action-history__entry span{font-weight:800}.absolute-voice-ops-action-history__entry small{color:#655944}.absolute-voice-ops-action-history__error{color:#9f1239;font-weight:700}`;
2690
+ var mountVoiceOpsActionHistory = (element, path = "/api/voice/ops-actions/history", options = {}) => {
2691
+ const store = createVoiceOpsActionHistoryStore(path, options);
2692
+ const render = () => {
2693
+ element.innerHTML = renderVoiceOpsActionHistoryWidgetHTML(store.getSnapshot(), options);
2694
+ };
2695
+ const unsubscribe = store.subscribe(render);
2696
+ render();
2697
+ store.refresh().catch(() => {});
2698
+ return {
2699
+ close: () => {
2700
+ unsubscribe();
2701
+ store.close();
2702
+ },
2703
+ refresh: store.refresh
2704
+ };
2705
+ };
2602
2706
  // src/client/deliveryRuntimeWidget.ts
2603
2707
  var DEFAULT_TITLE3 = "Voice Delivery Runtime";
2604
2708
  var DEFAULT_DESCRIPTION3 = "Audit and trace delivery worker health from your AbsoluteJS voice app.";
2605
- var escapeHtml3 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2709
+ var escapeHtml4 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2606
2710
  var createSurface = (id, summary) => {
2607
2711
  if (!summary) {
2608
2712
  return {
@@ -2651,26 +2755,26 @@ var createVoiceDeliveryRuntimeViewModel = (snapshot, options = {}) => {
2651
2755
  };
2652
2756
  var renderVoiceDeliveryRuntimeHTML = (snapshot, options = {}) => {
2653
2757
  const model = createVoiceDeliveryRuntimeViewModel(snapshot, options);
2654
- const surfaces = model.surfaces.map((surface) => `<li class="absolute-voice-delivery-runtime__surface absolute-voice-delivery-runtime__surface--${escapeHtml3(surface.status)}">
2655
- <span>${escapeHtml3(surface.label)}</span>
2656
- <strong>${escapeHtml3(surface.detail)}</strong>
2758
+ const surfaces = model.surfaces.map((surface) => `<li class="absolute-voice-delivery-runtime__surface absolute-voice-delivery-runtime__surface--${escapeHtml4(surface.status)}">
2759
+ <span>${escapeHtml4(surface.label)}</span>
2760
+ <strong>${escapeHtml4(surface.detail)}</strong>
2657
2761
  <small>${String(surface.failed)} failed &middot; ${String(surface.deadLettered)} dead-lettered</small>
2658
2762
  </li>`).join("");
2659
2763
  const actions = options.includeActions === false ? "" : `<div class="absolute-voice-delivery-runtime__actions">
2660
2764
  <button type="button" data-absolute-voice-delivery-runtime-action="tick">${model.actionStatus === "running" ? "Working..." : "Tick workers"}</button>
2661
2765
  <button type="button" data-absolute-voice-delivery-runtime-action="requeue-dead-letters"${model.surfaces.some((surface) => surface.deadLettered > 0) ? "" : " disabled"}>Requeue dead letters</button>
2662
2766
  </div>`;
2663
- const actionError = model.actionError ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml3(model.actionError)}</p>` : "";
2664
- return `<section class="absolute-voice-delivery-runtime absolute-voice-delivery-runtime--${escapeHtml3(model.status)}">
2767
+ const actionError = model.actionError ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml4(model.actionError)}</p>` : "";
2768
+ return `<section class="absolute-voice-delivery-runtime absolute-voice-delivery-runtime--${escapeHtml4(model.status)}">
2665
2769
  <header class="absolute-voice-delivery-runtime__header">
2666
- <span class="absolute-voice-delivery-runtime__eyebrow">${escapeHtml3(model.title)}</span>
2667
- <strong class="absolute-voice-delivery-runtime__label">${escapeHtml3(model.label)}</strong>
2770
+ <span class="absolute-voice-delivery-runtime__eyebrow">${escapeHtml4(model.title)}</span>
2771
+ <strong class="absolute-voice-delivery-runtime__label">${escapeHtml4(model.label)}</strong>
2668
2772
  </header>
2669
- <p class="absolute-voice-delivery-runtime__description">${escapeHtml3(model.description)}</p>
2773
+ <p class="absolute-voice-delivery-runtime__description">${escapeHtml4(model.description)}</p>
2670
2774
  <ul class="absolute-voice-delivery-runtime__surfaces">${surfaces}</ul>
2671
2775
  ${actions}
2672
2776
  ${actionError}
2673
- ${model.error ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml3(model.error)}</p>` : ""}
2777
+ ${model.error ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml4(model.error)}</p>` : ""}
2674
2778
  </section>`;
2675
2779
  };
2676
2780
  var getVoiceDeliveryRuntimeCSS = () => `.absolute-voice-delivery-runtime{border:1px solid #c9d8cf;border-radius:20px;background:#f6fff9;color:#0d1b12;padding:18px;box-shadow:0 18px 40px rgba(19,55,35,.12);font-family:inherit}.absolute-voice-delivery-runtime--warn,.absolute-voice-delivery-runtime--error{border-color:#f2b56b;background:#fff9ed}.absolute-voice-delivery-runtime__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-delivery-runtime__eyebrow{color:#4e6b59;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-delivery-runtime__label{font-size:28px;line-height:1}.absolute-voice-delivery-runtime__description{color:#33483b;margin:12px 0 0}.absolute-voice-delivery-runtime__surfaces{display:grid;gap:8px;list-style:none;margin:16px 0 0;padding:0}.absolute-voice-delivery-runtime__surface{background:#fff;border:1px solid #d9eadf;border-radius:14px;display:grid;gap:4px;padding:10px 12px}.absolute-voice-delivery-runtime__surface--warn{border-color:#f2b56b}.absolute-voice-delivery-runtime__surface--disabled{opacity:.72}.absolute-voice-delivery-runtime__surface span,.absolute-voice-delivery-runtime__surface small{color:#587063}.absolute-voice-delivery-runtime__actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.absolute-voice-delivery-runtime__actions button{background:#134e2d;border:0;border-radius:999px;color:#f6fff9;cursor:pointer;font:inherit;font-weight:800;padding:8px 12px}.absolute-voice-delivery-runtime__actions button:disabled{cursor:not-allowed;opacity:.48}.absolute-voice-delivery-runtime__error{color:#9f1239;font-weight:700}`;
@@ -2808,7 +2912,7 @@ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {})
2808
2912
  // src/client/routingStatusWidget.ts
2809
2913
  var DEFAULT_TITLE4 = "Voice Routing";
2810
2914
  var DEFAULT_DESCRIPTION4 = "Latest provider routing decision from the self-hosted trace store.";
2811
- var escapeHtml4 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2915
+ var escapeHtml5 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2812
2916
  var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
2813
2917
  var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
2814
2918
  const decision = snapshot.decision;
@@ -2845,17 +2949,17 @@ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
2845
2949
  var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
2846
2950
  const model = createVoiceRoutingStatusViewModel(snapshot, options);
2847
2951
  const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
2848
- <span>${escapeHtml4(row.label)}</span>
2849
- <strong>${escapeHtml4(row.value)}</strong>
2952
+ <span>${escapeHtml5(row.label)}</span>
2953
+ <strong>${escapeHtml5(row.value)}</strong>
2850
2954
  </div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
2851
- return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml4(model.status)}">
2955
+ return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml5(model.status)}">
2852
2956
  <header class="absolute-voice-routing-status__header">
2853
- <span class="absolute-voice-routing-status__eyebrow">${escapeHtml4(model.title)}</span>
2854
- <strong class="absolute-voice-routing-status__label">${escapeHtml4(model.label)}</strong>
2957
+ <span class="absolute-voice-routing-status__eyebrow">${escapeHtml5(model.title)}</span>
2958
+ <strong class="absolute-voice-routing-status__label">${escapeHtml5(model.label)}</strong>
2855
2959
  </header>
2856
- <p class="absolute-voice-routing-status__description">${escapeHtml4(model.description)}</p>
2960
+ <p class="absolute-voice-routing-status__description">${escapeHtml5(model.description)}</p>
2857
2961
  ${rows}
2858
- ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml4(model.error)}</p>` : ""}
2962
+ ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml5(model.error)}</p>` : ""}
2859
2963
  </section>`;
2860
2964
  };
2861
2965
  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__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}`;
@@ -3507,7 +3611,7 @@ var createVoiceProviderSimulationControlsStore = (options) => {
3507
3611
  };
3508
3612
  };
3509
3613
  // src/client/providerSimulationControlsWidget.ts
3510
- var escapeHtml5 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3614
+ var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3511
3615
  var formatKind = (kind) => (kind ?? "stt").toUpperCase();
3512
3616
  var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
3513
3617
  const configuredProviders = options.providers.filter((provider) => provider.configured !== false);
@@ -3527,18 +3631,18 @@ var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
3527
3631
  };
3528
3632
  var renderVoiceProviderSimulationControlsHTML = (snapshot, options) => {
3529
3633
  const model = createVoiceProviderSimulationControlsViewModel(snapshot, options);
3530
- const failureButtons = model.failureProviders.map((provider) => `<button type="button" data-voice-provider-fail="${escapeHtml5(provider.provider)}"${!model.canSimulateFailure || snapshot.isRunning ? " disabled" : ""}>Simulate ${escapeHtml5(provider.provider)} ${escapeHtml5(formatKind(options.kind))} failure</button>`).join("");
3531
- const recoveryButtons = model.providers.map((provider) => `<button type="button" data-voice-provider-recover="${escapeHtml5(provider.provider)}"${snapshot.isRunning ? " disabled" : ""}>Mark ${escapeHtml5(provider.provider)} recovered</button>`).join("");
3634
+ const failureButtons = model.failureProviders.map((provider) => `<button type="button" data-voice-provider-fail="${escapeHtml6(provider.provider)}"${!model.canSimulateFailure || snapshot.isRunning ? " disabled" : ""}>Simulate ${escapeHtml6(provider.provider)} ${escapeHtml6(formatKind(options.kind))} failure</button>`).join("");
3635
+ const recoveryButtons = model.providers.map((provider) => `<button type="button" data-voice-provider-recover="${escapeHtml6(provider.provider)}"${snapshot.isRunning ? " disabled" : ""}>Mark ${escapeHtml6(provider.provider)} recovered</button>`).join("");
3532
3636
  return `<section class="absolute-voice-provider-simulation absolute-voice-provider-simulation--${snapshot.error ? "error" : snapshot.isRunning ? "running" : "ready"}">
3533
3637
  <header class="absolute-voice-provider-simulation__header">
3534
- <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml5(model.title)}</span>
3535
- <strong class="absolute-voice-provider-simulation__label">${escapeHtml5(model.label)}</strong>
3638
+ <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml6(model.title)}</span>
3639
+ <strong class="absolute-voice-provider-simulation__label">${escapeHtml6(model.label)}</strong>
3536
3640
  </header>
3537
- <p class="absolute-voice-provider-simulation__description">${escapeHtml5(model.description)}</p>
3538
- ${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml5(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
3641
+ <p class="absolute-voice-provider-simulation__description">${escapeHtml6(model.description)}</p>
3642
+ ${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml6(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
3539
3643
  <div class="absolute-voice-provider-simulation__actions">${failureButtons}${recoveryButtons}</div>
3540
- ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml5(snapshot.error)}</p>` : ""}
3541
- ${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml5(model.resultText)}</pre>` : ""}
3644
+ ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml6(snapshot.error)}</p>` : ""}
3645
+ ${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml6(model.resultText)}</pre>` : ""}
3542
3646
  </section>`;
3543
3647
  };
3544
3648
  var bindVoiceProviderSimulationControls = (element, store) => {
@@ -3605,7 +3709,7 @@ var defineVoiceProviderSimulationControlsElement = (tagName = "absolute-voice-pr
3605
3709
  // src/client/providerStatusWidget.ts
3606
3710
  var DEFAULT_TITLE5 = "Voice Providers";
3607
3711
  var DEFAULT_DESCRIPTION5 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
3608
- var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3712
+ var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3609
3713
  var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
3610
3714
  var formatStatus = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
3611
3715
  var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
@@ -3661,25 +3765,25 @@ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
3661
3765
  };
3662
3766
  var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
3663
3767
  const model = createVoiceProviderStatusViewModel(snapshot, options);
3664
- 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--${escapeHtml6(provider.status)}">
3768
+ 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--${escapeHtml7(provider.status)}">
3665
3769
  <header>
3666
- <strong>${escapeHtml6(provider.label)}</strong>
3667
- <span>${escapeHtml6(formatStatus(provider.status))}</span>
3770
+ <strong>${escapeHtml7(provider.label)}</strong>
3771
+ <span>${escapeHtml7(formatStatus(provider.status))}</span>
3668
3772
  </header>
3669
- <p>${escapeHtml6(provider.detail)}</p>
3773
+ <p>${escapeHtml7(provider.detail)}</p>
3670
3774
  <dl>${provider.rows.map((row) => `<div>
3671
- <dt>${escapeHtml6(row.label)}</dt>
3672
- <dd>${escapeHtml6(row.value)}</dd>
3775
+ <dt>${escapeHtml7(row.label)}</dt>
3776
+ <dd>${escapeHtml7(row.value)}</dd>
3673
3777
  </div>`).join("")}</dl>
3674
3778
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
3675
- return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml6(model.status)}">
3779
+ return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml7(model.status)}">
3676
3780
  <header class="absolute-voice-provider-status__header">
3677
- <span class="absolute-voice-provider-status__eyebrow">${escapeHtml6(model.title)}</span>
3678
- <strong class="absolute-voice-provider-status__label">${escapeHtml6(model.label)}</strong>
3781
+ <span class="absolute-voice-provider-status__eyebrow">${escapeHtml7(model.title)}</span>
3782
+ <strong class="absolute-voice-provider-status__label">${escapeHtml7(model.label)}</strong>
3679
3783
  </header>
3680
- <p class="absolute-voice-provider-status__description">${escapeHtml6(model.description)}</p>
3784
+ <p class="absolute-voice-provider-status__description">${escapeHtml7(model.description)}</p>
3681
3785
  ${providers}
3682
- ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml6(model.error)}</p>` : ""}
3786
+ ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml7(model.error)}</p>` : ""}
3683
3787
  </section>`;
3684
3788
  };
3685
3789
  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}`;
@@ -3722,7 +3826,7 @@ var defineVoiceProviderStatusElement = (tagName = "absolute-voice-provider-statu
3722
3826
  // src/client/providerCapabilitiesWidget.ts
3723
3827
  var DEFAULT_TITLE6 = "Provider Capabilities";
3724
3828
  var DEFAULT_DESCRIPTION6 = "Configured, selected, and healthy voice providers for this deployment.";
3725
- var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3829
+ var escapeHtml8 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3726
3830
  var formatProvider2 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
3727
3831
  var formatKind2 = (kind) => kind.toUpperCase();
3728
3832
  var formatStatus2 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
@@ -3777,25 +3881,25 @@ var createVoiceProviderCapabilitiesViewModel = (snapshot, options = {}) => {
3777
3881
  };
3778
3882
  var renderVoiceProviderCapabilitiesHTML = (snapshot, options = {}) => {
3779
3883
  const model = createVoiceProviderCapabilitiesViewModel(snapshot, options);
3780
- 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--${escapeHtml7(capability.status)}">
3884
+ 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--${escapeHtml8(capability.status)}">
3781
3885
  <header>
3782
- <strong>${escapeHtml7(capability.label)}</strong>
3783
- <span>${escapeHtml7(formatStatus2(capability.status))}</span>
3886
+ <strong>${escapeHtml8(capability.label)}</strong>
3887
+ <span>${escapeHtml8(formatStatus2(capability.status))}</span>
3784
3888
  </header>
3785
- <p>${escapeHtml7(capability.detail)}</p>
3889
+ <p>${escapeHtml8(capability.detail)}</p>
3786
3890
  <dl>${capability.rows.map((row) => `<div>
3787
- <dt>${escapeHtml7(row.label)}</dt>
3788
- <dd>${escapeHtml7(row.value)}</dd>
3891
+ <dt>${escapeHtml8(row.label)}</dt>
3892
+ <dd>${escapeHtml8(row.value)}</dd>
3789
3893
  </div>`).join("")}</dl>
3790
3894
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-capabilities__empty">Configure provider capabilities to see deployment coverage.</p>';
3791
- return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml7(model.status)}">
3895
+ return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml8(model.status)}">
3792
3896
  <header class="absolute-voice-provider-capabilities__header">
3793
- <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml7(model.title)}</span>
3794
- <strong class="absolute-voice-provider-capabilities__label">${escapeHtml7(model.label)}</strong>
3897
+ <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml8(model.title)}</span>
3898
+ <strong class="absolute-voice-provider-capabilities__label">${escapeHtml8(model.label)}</strong>
3795
3899
  </header>
3796
- <p class="absolute-voice-provider-capabilities__description">${escapeHtml7(model.description)}</p>
3900
+ <p class="absolute-voice-provider-capabilities__description">${escapeHtml8(model.description)}</p>
3797
3901
  ${capabilities}
3798
- ${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml7(model.error)}</p>` : ""}
3902
+ ${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml8(model.error)}</p>` : ""}
3799
3903
  </section>`;
3800
3904
  };
3801
3905
  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}`;
@@ -3838,7 +3942,7 @@ var defineVoiceProviderCapabilitiesElement = (tagName = "absolute-voice-provider
3838
3942
  // src/client/turnQualityWidget.ts
3839
3943
  var DEFAULT_TITLE7 = "Turn Quality";
3840
3944
  var DEFAULT_DESCRIPTION7 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
3841
- var escapeHtml8 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3945
+ var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3842
3946
  var formatConfidence = (value) => typeof value === "number" ? `${Math.round(value * 100)}%` : "n/a";
3843
3947
  var formatMaybe = (value) => value === undefined || value === "" ? "n/a" : String(value);
3844
3948
  var getTurnDetail = (turn) => {
@@ -3888,25 +3992,25 @@ var createVoiceTurnQualityViewModel = (snapshot, options = {}) => {
3888
3992
  };
3889
3993
  var renderVoiceTurnQualityHTML = (snapshot, options = {}) => {
3890
3994
  const model = createVoiceTurnQualityViewModel(snapshot, options);
3891
- 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--${escapeHtml8(turn.status)}">
3995
+ 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--${escapeHtml9(turn.status)}">
3892
3996
  <header>
3893
- <strong>${escapeHtml8(turn.label)}</strong>
3894
- <span>${escapeHtml8(turn.status)}</span>
3997
+ <strong>${escapeHtml9(turn.label)}</strong>
3998
+ <span>${escapeHtml9(turn.status)}</span>
3895
3999
  </header>
3896
- <p>${escapeHtml8(turn.detail)}</p>
4000
+ <p>${escapeHtml9(turn.detail)}</p>
3897
4001
  <dl>${turn.rows.map((row) => `<div>
3898
- <dt>${escapeHtml8(row.label)}</dt>
3899
- <dd>${escapeHtml8(row.value)}</dd>
4002
+ <dt>${escapeHtml9(row.label)}</dt>
4003
+ <dd>${escapeHtml9(row.value)}</dd>
3900
4004
  </div>`).join("")}</dl>
3901
4005
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-quality__empty">Complete a voice turn to see STT quality diagnostics.</p>';
3902
- return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml8(model.status)}">
4006
+ return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml9(model.status)}">
3903
4007
  <header class="absolute-voice-turn-quality__header">
3904
- <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml8(model.title)}</span>
3905
- <strong class="absolute-voice-turn-quality__label">${escapeHtml8(model.label)}</strong>
4008
+ <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml9(model.title)}</span>
4009
+ <strong class="absolute-voice-turn-quality__label">${escapeHtml9(model.label)}</strong>
3906
4010
  </header>
3907
- <p class="absolute-voice-turn-quality__description">${escapeHtml8(model.description)}</p>
4011
+ <p class="absolute-voice-turn-quality__description">${escapeHtml9(model.description)}</p>
3908
4012
  ${turns}
3909
- ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml8(model.error)}</p>` : ""}
4013
+ ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml9(model.error)}</p>` : ""}
3910
4014
  </section>`;
3911
4015
  };
3912
4016
  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}`;
@@ -3950,7 +4054,7 @@ var defineVoiceTurnQualityElement = (tagName = "absolute-voice-turn-quality") =>
3950
4054
  var DEFAULT_TITLE8 = "Turn Latency";
3951
4055
  var DEFAULT_DESCRIPTION8 = "Per-turn timing from first transcript to commit and assistant response start.";
3952
4056
  var DEFAULT_PROOF_LABEL = "Run latency proof";
3953
- var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4057
+ var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3954
4058
  var formatMs = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
3955
4059
  var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
3956
4060
  const turns = (snapshot.report?.turns ?? []).map((turn) => ({
@@ -3978,25 +4082,25 @@ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
3978
4082
  };
3979
4083
  var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
3980
4084
  const model = createVoiceTurnLatencyViewModel(snapshot, options);
3981
- 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--${escapeHtml9(turn.status)}">
4085
+ 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--${escapeHtml10(turn.status)}">
3982
4086
  <header>
3983
- <strong>${escapeHtml9(turn.label)}</strong>
3984
- <span>${escapeHtml9(turn.status)}</span>
4087
+ <strong>${escapeHtml10(turn.label)}</strong>
4088
+ <span>${escapeHtml10(turn.status)}</span>
3985
4089
  </header>
3986
4090
  <dl>${turn.rows.map((row) => `<div>
3987
- <dt>${escapeHtml9(row.label)}</dt>
3988
- <dd>${escapeHtml9(row.value)}</dd>
4091
+ <dt>${escapeHtml10(row.label)}</dt>
4092
+ <dd>${escapeHtml10(row.value)}</dd>
3989
4093
  </div>`).join("")}</dl>
3990
4094
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-latency__empty">Complete a voice turn to see latency diagnostics.</p>';
3991
- return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml9(model.status)}">
4095
+ return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml10(model.status)}">
3992
4096
  <header class="absolute-voice-turn-latency__header">
3993
- <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml9(model.title)}</span>
3994
- <strong class="absolute-voice-turn-latency__label">${escapeHtml9(model.label)}</strong>
4097
+ <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml10(model.title)}</span>
4098
+ <strong class="absolute-voice-turn-latency__label">${escapeHtml10(model.label)}</strong>
3995
4099
  </header>
3996
- <p class="absolute-voice-turn-latency__description">${escapeHtml9(model.description)}</p>
3997
- ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml9(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
4100
+ <p class="absolute-voice-turn-latency__description">${escapeHtml10(model.description)}</p>
4101
+ ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml10(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
3998
4102
  ${turns}
3999
- ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml9(model.error)}</p>` : ""}
4103
+ ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml10(model.error)}</p>` : ""}
4000
4104
  </section>`;
4001
4105
  };
4002
4106
  var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {}) => {
@@ -4048,7 +4152,7 @@ var defineVoiceTurnLatencyElement = (tagName = "absolute-voice-turn-latency") =>
4048
4152
  // src/client/traceTimelineWidget.ts
4049
4153
  var DEFAULT_TITLE9 = "Voice Traces";
4050
4154
  var DEFAULT_DESCRIPTION9 = "Latest call timelines with provider latency, fallbacks, handoffs, and errors from your self-hosted trace store.";
4051
- var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4155
+ var escapeHtml11 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4052
4156
  var formatMs2 = (value) => typeof value === "number" ? `${value}ms` : "n/a";
4053
4157
  var formatProviders = (session) => session.providers.length ? session.providers.map((provider) => provider.provider).join(", ") : "No providers";
4054
4158
  var createVoiceTraceTimelineViewModel = (snapshot, options = {}) => {
@@ -4074,22 +4178,22 @@ var createVoiceTraceTimelineViewModel = (snapshot, options = {}) => {
4074
4178
  };
4075
4179
  var renderVoiceTraceTimelineWidgetHTML = (snapshot, options = {}) => {
4076
4180
  const model = createVoiceTraceTimelineViewModel(snapshot, options);
4077
- const sessions = model.sessions.length ? `<div class="absolute-voice-trace-timeline__sessions">${model.sessions.map((session) => `<article class="absolute-voice-trace-timeline__session absolute-voice-trace-timeline__session--${escapeHtml10(session.status)}">
4181
+ const sessions = model.sessions.length ? `<div class="absolute-voice-trace-timeline__sessions">${model.sessions.map((session) => `<article class="absolute-voice-trace-timeline__session absolute-voice-trace-timeline__session--${escapeHtml11(session.status)}">
4078
4182
  <header>
4079
- <strong>${escapeHtml10(session.sessionId)}</strong>
4080
- <span>${escapeHtml10(session.status)}</span>
4183
+ <strong>${escapeHtml11(session.sessionId)}</strong>
4184
+ <span>${escapeHtml11(session.status)}</span>
4081
4185
  </header>
4082
- <p>${escapeHtml10(session.label)} \xB7 ${escapeHtml10(session.durationLabel)} \xB7 ${escapeHtml10(session.providerLabel)}</p>
4083
- <a href="${escapeHtml10(session.detailHref)}">Open timeline</a>
4186
+ <p>${escapeHtml11(session.label)} \xB7 ${escapeHtml11(session.durationLabel)} \xB7 ${escapeHtml11(session.providerLabel)}</p>
4187
+ <a href="${escapeHtml11(session.detailHref)}">Open timeline</a>
4084
4188
  </article>`).join("")}</div>` : '<p class="absolute-voice-trace-timeline__empty">Run a voice session to see call timelines.</p>';
4085
- return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml10(model.status)}">
4189
+ return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml11(model.status)}">
4086
4190
  <header class="absolute-voice-trace-timeline__header">
4087
- <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml10(model.title)}</span>
4088
- <strong class="absolute-voice-trace-timeline__label">${escapeHtml10(model.label)}</strong>
4191
+ <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml11(model.title)}</span>
4192
+ <strong class="absolute-voice-trace-timeline__label">${escapeHtml11(model.label)}</strong>
4089
4193
  </header>
4090
- <p class="absolute-voice-trace-timeline__description">${escapeHtml10(model.description)}</p>
4194
+ <p class="absolute-voice-trace-timeline__description">${escapeHtml11(model.description)}</p>
4091
4195
  ${sessions}
4092
- ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml10(model.error)}</p>` : ""}
4196
+ ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml11(model.error)}</p>` : ""}
4093
4197
  </section>`;
4094
4198
  };
4095
4199
  var getVoiceTraceTimelineCSS = () => `.absolute-voice-trace-timeline{border:1px solid #bad7d3;border-radius:20px;background:#f3fffb;color:#09201c;padding:18px;box-shadow:0 18px 40px rgba(9,32,28,.12);font-family:inherit}.absolute-voice-trace-timeline--error,.absolute-voice-trace-timeline--failed{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-trace-timeline--warning{border-color:#fbbf24;background:#fffaf0}.absolute-voice-trace-timeline__header,.absolute-voice-trace-timeline__session header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-trace-timeline__eyebrow{color:#17665b;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-trace-timeline__label{font-size:24px;line-height:1}.absolute-voice-trace-timeline__description,.absolute-voice-trace-timeline__session p,.absolute-voice-trace-timeline__empty{color:#35544f}.absolute-voice-trace-timeline__sessions{display:grid;gap:12px;margin-top:14px}.absolute-voice-trace-timeline__session{background:#fff;border:1px solid #cfe7e2;border-radius:16px;padding:14px}.absolute-voice-trace-timeline__session--failed{border-color:#f2a7a7}.absolute-voice-trace-timeline__session--warning{border-color:#fbbf24}.absolute-voice-trace-timeline__session p{margin:10px 0}.absolute-voice-trace-timeline__session a{color:#0f766e;font-weight:800}.absolute-voice-trace-timeline__empty{margin:14px 0 0}.absolute-voice-trace-timeline__error{color:#9f1239;font-weight:700}`;
@@ -4223,6 +4327,7 @@ export {
4223
4327
  renderVoiceProviderSimulationControlsHTML,
4224
4328
  renderVoiceProviderCapabilitiesHTML,
4225
4329
  renderVoiceOpsStatusHTML,
4330
+ renderVoiceOpsActionHistoryWidgetHTML,
4226
4331
  renderVoiceOpsActionCenterHTML,
4227
4332
  renderVoiceDeliveryRuntimeHTML,
4228
4333
  recordVoiceOpsActionResult,
@@ -4234,6 +4339,7 @@ export {
4234
4339
  mountVoiceProviderSimulationControls,
4235
4340
  mountVoiceProviderCapabilities,
4236
4341
  mountVoiceOpsStatus,
4342
+ mountVoiceOpsActionHistory,
4237
4343
  mountVoiceOpsActionCenter,
4238
4344
  mountVoiceDeliveryRuntime,
4239
4345
  getVoiceTurnQualityCSS,
@@ -4243,6 +4349,7 @@ export {
4243
4349
  getVoiceProviderCapabilitiesCSS,
4244
4350
  getVoiceOpsStatusLabel,
4245
4351
  getVoiceOpsStatusCSS,
4352
+ getVoiceOpsActionHistoryCSS,
4246
4353
  getVoiceOpsActionCenterCSS,
4247
4354
  getVoiceDeliveryRuntimeCSS,
4248
4355
  fetchVoiceWorkflowStatus,
@@ -4253,6 +4360,7 @@ export {
4253
4360
  fetchVoiceProviderStatus,
4254
4361
  fetchVoiceProviderCapabilities,
4255
4362
  fetchVoiceOpsStatus,
4363
+ fetchVoiceOpsActionHistory,
4256
4364
  fetchVoiceDeliveryRuntime,
4257
4365
  fetchVoiceCampaignDialerProofStatus,
4258
4366
  defineVoiceTurnQualityElement,
@@ -4284,6 +4392,7 @@ export {
4284
4392
  createVoiceProviderCapabilitiesStore,
4285
4393
  createVoiceOpsStatusViewModel,
4286
4394
  createVoiceOpsStatusStore,
4395
+ createVoiceOpsActionHistoryStore,
4287
4396
  createVoiceOpsActionCenterViewModel,
4288
4397
  createVoiceOpsActionCenterStore,
4289
4398
  createVoiceOpsActionCenterActions,
@@ -0,0 +1,19 @@
1
+ import type { VoiceOpsActionHistoryReport } from '../opsActionAuditRoutes';
2
+ export type VoiceOpsActionHistoryClientOptions = {
3
+ fetch?: typeof fetch;
4
+ intervalMs?: number;
5
+ };
6
+ export type VoiceOpsActionHistorySnapshot = {
7
+ error: string | null;
8
+ isLoading: boolean;
9
+ report?: VoiceOpsActionHistoryReport;
10
+ updatedAt?: number;
11
+ };
12
+ export declare const fetchVoiceOpsActionHistory: (path?: string, options?: Pick<VoiceOpsActionHistoryClientOptions, "fetch">) => Promise<VoiceOpsActionHistoryReport>;
13
+ export declare const createVoiceOpsActionHistoryStore: (path?: string, options?: VoiceOpsActionHistoryClientOptions) => {
14
+ close: () => void;
15
+ getServerSnapshot: () => VoiceOpsActionHistorySnapshot;
16
+ getSnapshot: () => VoiceOpsActionHistorySnapshot;
17
+ refresh: () => Promise<VoiceOpsActionHistoryReport | undefined>;
18
+ subscribe: (listener: () => void) => () => void;
19
+ };
@@ -0,0 +1,11 @@
1
+ import { type VoiceOpsActionHistoryClientOptions, type VoiceOpsActionHistorySnapshot } from './opsActionHistory';
2
+ export type VoiceOpsActionHistoryWidgetOptions = VoiceOpsActionHistoryClientOptions & {
3
+ limit?: number;
4
+ title?: string;
5
+ };
6
+ export declare const renderVoiceOpsActionHistoryWidgetHTML: (snapshot: VoiceOpsActionHistorySnapshot, options?: VoiceOpsActionHistoryWidgetOptions) => string;
7
+ export declare const getVoiceOpsActionHistoryCSS: () => string;
8
+ export declare const mountVoiceOpsActionHistory: (element: Element, path?: string, options?: VoiceOpsActionHistoryWidgetOptions) => {
9
+ close: () => void;
10
+ refresh: () => Promise<import("..").VoiceOpsActionHistoryReport | undefined>;
11
+ };