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

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.
@@ -2062,6 +2062,196 @@ var createVoiceOpsStatusStore = (path = "/api/voice/ops-status", options = {}) =
2062
2062
  }
2063
2063
  };
2064
2064
  };
2065
+ // src/client/opsActionCenter.ts
2066
+ var recordVoiceOpsActionResult = async (result, options = {}) => {
2067
+ if (options.auditPath === false) {
2068
+ return;
2069
+ }
2070
+ const path = options.auditPath ?? "/api/voice/ops-actions/audit";
2071
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2072
+ const response = await fetchImpl(path, {
2073
+ body: JSON.stringify(result),
2074
+ headers: {
2075
+ "Content-Type": "application/json"
2076
+ },
2077
+ method: "POST"
2078
+ });
2079
+ if (!response.ok) {
2080
+ throw new Error(`Voice ops action audit failed: HTTP ${response.status}`);
2081
+ }
2082
+ };
2083
+ var createVoiceOpsActionCenterActions = (options = {}) => {
2084
+ const deliveryRuntimePath = options.deliveryRuntimePath ?? "/api/voice-delivery-runtime";
2085
+ const actions = [];
2086
+ if (options.includeProductionReadiness !== false) {
2087
+ actions.push({
2088
+ description: "Refresh the production readiness report.",
2089
+ id: "production-readiness",
2090
+ label: "Refresh readiness",
2091
+ method: "GET",
2092
+ path: options.productionReadinessPath ?? "/api/production-readiness"
2093
+ });
2094
+ }
2095
+ if (options.includeDeliveryRuntime !== false) {
2096
+ actions.push({
2097
+ description: "Drain pending and failed audit/trace deliveries.",
2098
+ id: "delivery-runtime.tick",
2099
+ label: "Tick delivery workers",
2100
+ method: "POST",
2101
+ path: `${deliveryRuntimePath.replace(/\/$/, "")}/tick`
2102
+ }, {
2103
+ description: "Move reviewed dead letters back to live delivery queues.",
2104
+ id: "delivery-runtime.requeue-dead-letters",
2105
+ label: "Requeue dead letters",
2106
+ method: "POST",
2107
+ path: `${deliveryRuntimePath.replace(/\/$/, "")}/requeue-dead-letters`
2108
+ });
2109
+ }
2110
+ if (options.includeTurnLatencyProof !== false) {
2111
+ actions.push({
2112
+ description: "Run the synthetic turn latency proof.",
2113
+ id: "turn-latency.proof",
2114
+ label: "Run latency proof",
2115
+ method: "POST",
2116
+ path: options.turnLatencyProofPath ?? "/api/turn-latency/proof"
2117
+ });
2118
+ }
2119
+ if (options.includeProviderSimulation !== false) {
2120
+ const pathPrefix = options.providerSimulationPathPrefix ?? "/api/stt-simulate";
2121
+ for (const provider of options.providers ?? []) {
2122
+ actions.push({
2123
+ description: `Simulate ${provider} provider failure.`,
2124
+ id: `provider.${provider}.failure`,
2125
+ label: `Simulate ${provider} failure`,
2126
+ method: "POST",
2127
+ path: `${pathPrefix}/failure?provider=${encodeURIComponent(provider)}`
2128
+ }, {
2129
+ description: `Mark ${provider} provider recovered.`,
2130
+ id: `provider.${provider}.recovery`,
2131
+ label: `Recover ${provider}`,
2132
+ method: "POST",
2133
+ path: `${pathPrefix}/recovery?provider=${encodeURIComponent(provider)}`
2134
+ });
2135
+ }
2136
+ }
2137
+ return actions;
2138
+ };
2139
+ var runVoiceOpsAction = async (action, options = {}) => {
2140
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2141
+ const response = await fetchImpl(action.path, {
2142
+ method: action.method ?? "POST"
2143
+ });
2144
+ const body = await response.json().catch(() => null);
2145
+ if (!response.ok) {
2146
+ const message = body && typeof body === "object" && "error" in body ? String(body.error) : `Voice ops action "${action.id}" failed: HTTP ${response.status}`;
2147
+ throw new Error(message);
2148
+ }
2149
+ return {
2150
+ actionId: action.id,
2151
+ body,
2152
+ ok: response.ok,
2153
+ ranAt: Date.now(),
2154
+ status: response.status
2155
+ };
2156
+ };
2157
+ var createVoiceOpsActionCenterStore = (options = {}) => {
2158
+ const listeners = new Set;
2159
+ let closed = false;
2160
+ let timer;
2161
+ let snapshot = {
2162
+ actions: options.actions ?? createVoiceOpsActionCenterActions(),
2163
+ error: null,
2164
+ isRunning: false
2165
+ };
2166
+ const emit = () => {
2167
+ for (const listener of listeners) {
2168
+ listener();
2169
+ }
2170
+ };
2171
+ const setActions = (actions) => {
2172
+ snapshot = { ...snapshot, actions, updatedAt: Date.now() };
2173
+ emit();
2174
+ };
2175
+ const run = async (actionId) => {
2176
+ if (closed) {
2177
+ return snapshot.lastResult;
2178
+ }
2179
+ const action = snapshot.actions.find((item) => item.id === actionId);
2180
+ if (!action) {
2181
+ throw new Error(`Voice ops action "${actionId}" is not configured.`);
2182
+ }
2183
+ if (action.disabled) {
2184
+ throw new Error(`Voice ops action "${actionId}" is disabled.`);
2185
+ }
2186
+ snapshot = {
2187
+ ...snapshot,
2188
+ error: null,
2189
+ isRunning: true,
2190
+ runningActionId: action.id
2191
+ };
2192
+ emit();
2193
+ try {
2194
+ const result = await runVoiceOpsAction(action, options);
2195
+ await options.onActionResult?.(result);
2196
+ await recordVoiceOpsActionResult(result, options);
2197
+ snapshot = {
2198
+ ...snapshot,
2199
+ error: null,
2200
+ isRunning: false,
2201
+ lastResult: result,
2202
+ runningActionId: undefined,
2203
+ updatedAt: Date.now()
2204
+ };
2205
+ emit();
2206
+ return result;
2207
+ } catch (error) {
2208
+ const result = {
2209
+ actionId: action.id,
2210
+ body: null,
2211
+ error: error instanceof Error ? error.message : String(error),
2212
+ ok: false,
2213
+ ranAt: Date.now(),
2214
+ status: 0
2215
+ };
2216
+ await options.onActionResult?.(result);
2217
+ await recordVoiceOpsActionResult(result, options).catch(() => {});
2218
+ snapshot = {
2219
+ ...snapshot,
2220
+ error: error instanceof Error ? error.message : String(error),
2221
+ isRunning: false,
2222
+ runningActionId: undefined
2223
+ };
2224
+ emit();
2225
+ throw error;
2226
+ }
2227
+ };
2228
+ const close = () => {
2229
+ closed = true;
2230
+ if (timer) {
2231
+ clearInterval(timer);
2232
+ timer = undefined;
2233
+ }
2234
+ listeners.clear();
2235
+ };
2236
+ if (options.intervalMs && options.intervalMs > 0) {
2237
+ timer = setInterval(() => {
2238
+ emit();
2239
+ }, options.intervalMs);
2240
+ }
2241
+ return {
2242
+ close,
2243
+ getServerSnapshot: () => snapshot,
2244
+ getSnapshot: () => snapshot,
2245
+ run,
2246
+ setActions,
2247
+ subscribe: (listener) => {
2248
+ listeners.add(listener);
2249
+ return () => {
2250
+ listeners.delete(listener);
2251
+ };
2252
+ }
2253
+ };
2254
+ };
2065
2255
  // src/client/deliveryRuntime.ts
2066
2256
  var getDefaultActionPath = (path, action, options) => {
2067
2257
  if (action === "tick") {
@@ -2322,10 +2512,97 @@ var defineVoiceOpsStatusElement = (tagName = "absolute-voice-ops-status") => {
2322
2512
  }
2323
2513
  });
2324
2514
  };
2325
- // src/client/deliveryRuntimeWidget.ts
2326
- var DEFAULT_TITLE2 = "Voice Delivery Runtime";
2327
- var DEFAULT_DESCRIPTION2 = "Audit and trace delivery worker health from your AbsoluteJS voice app.";
2515
+ // src/client/opsActionCenterWidget.ts
2516
+ var DEFAULT_TITLE2 = "Voice Ops Action Center";
2517
+ var DEFAULT_DESCRIPTION2 = "Run production voice proofs and operator actions from one primitive panel.";
2328
2518
  var escapeHtml2 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2519
+ var createVoiceOpsActionCenterViewModel = (snapshot, options = {}) => {
2520
+ const status = snapshot.error ? "error" : snapshot.isRunning ? "running" : snapshot.lastResult ? "completed" : "ready";
2521
+ return {
2522
+ actions: snapshot.actions.map((action) => ({
2523
+ description: action.description ?? "",
2524
+ disabled: Boolean(action.disabled || snapshot.isRunning),
2525
+ id: action.id,
2526
+ isRunning: snapshot.runningActionId === action.id,
2527
+ label: action.label
2528
+ })),
2529
+ description: options.description ?? DEFAULT_DESCRIPTION2,
2530
+ error: snapshot.error,
2531
+ isRunning: snapshot.isRunning,
2532
+ label: status === "error" ? "Needs attention" : status === "running" ? "Running" : status === "completed" ? "Action completed" : "Ready",
2533
+ lastResultLabel: snapshot.lastResult ? `${snapshot.lastResult.actionId} returned HTTP ${snapshot.lastResult.status}` : "No action has run yet.",
2534
+ status,
2535
+ title: options.title ?? DEFAULT_TITLE2
2536
+ };
2537
+ };
2538
+ var renderVoiceOpsActionCenterHTML = (snapshot, options = {}) => {
2539
+ const model = createVoiceOpsActionCenterViewModel(snapshot, options);
2540
+ const actions = model.actions.map((action) => `<button type="button" data-absolute-voice-ops-action="${escapeHtml2(action.id)}"${action.disabled ? " disabled" : ""}>
2541
+ ${escapeHtml2(action.isRunning ? "Working..." : action.label)}
2542
+ </button>`).join("");
2543
+ return `<section class="absolute-voice-ops-action-center absolute-voice-ops-action-center--${escapeHtml2(model.status)}">
2544
+ <header class="absolute-voice-ops-action-center__header">
2545
+ <span class="absolute-voice-ops-action-center__eyebrow">${escapeHtml2(model.title)}</span>
2546
+ <strong class="absolute-voice-ops-action-center__label">${escapeHtml2(model.label)}</strong>
2547
+ </header>
2548
+ <p class="absolute-voice-ops-action-center__description">${escapeHtml2(model.description)}</p>
2549
+ <div class="absolute-voice-ops-action-center__actions">${actions}</div>
2550
+ <p class="absolute-voice-ops-action-center__result">${escapeHtml2(model.lastResultLabel)}</p>
2551
+ ${model.error ? `<p class="absolute-voice-ops-action-center__error">${escapeHtml2(model.error)}</p>` : ""}
2552
+ </section>`;
2553
+ };
2554
+ var getVoiceOpsActionCenterCSS = () => `.absolute-voice-ops-action-center{border:1px solid #d5cbb8;border-radius:20px;background:#fffaf1;color:#17130b;padding:18px;box-shadow:0 18px 40px rgba(58,42,16,.12);font-family:inherit}.absolute-voice-ops-action-center--error{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-ops-action-center__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-ops-action-center__eyebrow{color:#725d37;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-ops-action-center__label{font-size:28px;line-height:1}.absolute-voice-ops-action-center__description,.absolute-voice-ops-action-center__result{color:#5b4b2f;margin:12px 0 0}.absolute-voice-ops-action-center__actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.absolute-voice-ops-action-center__actions button{background:#7c4a03;border:0;border-radius:999px;color:#fff8e8;cursor:pointer;font:inherit;font-weight:800;padding:8px 12px}.absolute-voice-ops-action-center__actions button:disabled{cursor:not-allowed;opacity:.5}.absolute-voice-ops-action-center__error{color:#9f1239;font-weight:700}`;
2555
+ var mountVoiceOpsActionCenter = (element, options = {}) => {
2556
+ const store = createVoiceOpsActionCenterStore(options);
2557
+ const render = () => {
2558
+ element.innerHTML = renderVoiceOpsActionCenterHTML(store.getSnapshot(), options);
2559
+ };
2560
+ const unsubscribe = store.subscribe(render);
2561
+ const handleClick = (event) => {
2562
+ const target = event.target;
2563
+ if (!(target instanceof Element)) {
2564
+ return;
2565
+ }
2566
+ const action = target.closest("[data-absolute-voice-ops-action]");
2567
+ const actionId = action?.getAttribute("data-absolute-voice-ops-action");
2568
+ if (actionId) {
2569
+ store.run(actionId).catch(() => {});
2570
+ }
2571
+ };
2572
+ element.addEventListener?.("click", handleClick);
2573
+ render();
2574
+ return {
2575
+ close: () => {
2576
+ element.removeEventListener?.("click", handleClick);
2577
+ unsubscribe();
2578
+ store.close();
2579
+ },
2580
+ run: store.run
2581
+ };
2582
+ };
2583
+ var defineVoiceOpsActionCenterElement = (tagName = "absolute-voice-ops-action-center", options = {}) => {
2584
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
2585
+ return;
2586
+ }
2587
+ customElements.define(tagName, class AbsoluteVoiceOpsActionCenterElement extends HTMLElement {
2588
+ mounted;
2589
+ connectedCallback() {
2590
+ this.mounted = mountVoiceOpsActionCenter(this, {
2591
+ ...options,
2592
+ description: this.getAttribute("description") ?? options.description,
2593
+ title: this.getAttribute("title") ?? options.title
2594
+ });
2595
+ }
2596
+ disconnectedCallback() {
2597
+ this.mounted?.close();
2598
+ this.mounted = undefined;
2599
+ }
2600
+ });
2601
+ };
2602
+ // src/client/deliveryRuntimeWidget.ts
2603
+ var DEFAULT_TITLE3 = "Voice Delivery Runtime";
2604
+ 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;");
2329
2606
  var createSurface = (id, summary) => {
2330
2607
  if (!summary) {
2331
2608
  return {
@@ -2359,7 +2636,7 @@ var createVoiceDeliveryRuntimeViewModel = (snapshot, options = {}) => {
2359
2636
  ];
2360
2637
  const hasWarnings = surfaces.some((surface) => surface.status === "warn");
2361
2638
  return {
2362
- description: options.description ?? DEFAULT_DESCRIPTION2,
2639
+ description: options.description ?? DEFAULT_DESCRIPTION3,
2363
2640
  error: snapshot.error,
2364
2641
  actionError: snapshot.actionError,
2365
2642
  actionStatus: snapshot.actionStatus,
@@ -2368,32 +2645,32 @@ var createVoiceDeliveryRuntimeViewModel = (snapshot, options = {}) => {
2368
2645
  label: snapshot.error ? "Unavailable" : report ? report.isRunning ? "Running" : "Stopped" : "Checking",
2369
2646
  status: snapshot.error ? "error" : report ? hasWarnings ? "warn" : "pass" : "loading",
2370
2647
  surfaces,
2371
- title: options.title ?? DEFAULT_TITLE2,
2648
+ title: options.title ?? DEFAULT_TITLE3,
2372
2649
  updatedAt: snapshot.updatedAt
2373
2650
  };
2374
2651
  };
2375
2652
  var renderVoiceDeliveryRuntimeHTML = (snapshot, options = {}) => {
2376
2653
  const model = createVoiceDeliveryRuntimeViewModel(snapshot, options);
2377
- const surfaces = model.surfaces.map((surface) => `<li class="absolute-voice-delivery-runtime__surface absolute-voice-delivery-runtime__surface--${escapeHtml2(surface.status)}">
2378
- <span>${escapeHtml2(surface.label)}</span>
2379
- <strong>${escapeHtml2(surface.detail)}</strong>
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>
2380
2657
  <small>${String(surface.failed)} failed &middot; ${String(surface.deadLettered)} dead-lettered</small>
2381
2658
  </li>`).join("");
2382
2659
  const actions = options.includeActions === false ? "" : `<div class="absolute-voice-delivery-runtime__actions">
2383
2660
  <button type="button" data-absolute-voice-delivery-runtime-action="tick">${model.actionStatus === "running" ? "Working..." : "Tick workers"}</button>
2384
2661
  <button type="button" data-absolute-voice-delivery-runtime-action="requeue-dead-letters"${model.surfaces.some((surface) => surface.deadLettered > 0) ? "" : " disabled"}>Requeue dead letters</button>
2385
2662
  </div>`;
2386
- const actionError = model.actionError ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml2(model.actionError)}</p>` : "";
2387
- return `<section class="absolute-voice-delivery-runtime absolute-voice-delivery-runtime--${escapeHtml2(model.status)}">
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)}">
2388
2665
  <header class="absolute-voice-delivery-runtime__header">
2389
- <span class="absolute-voice-delivery-runtime__eyebrow">${escapeHtml2(model.title)}</span>
2390
- <strong class="absolute-voice-delivery-runtime__label">${escapeHtml2(model.label)}</strong>
2666
+ <span class="absolute-voice-delivery-runtime__eyebrow">${escapeHtml3(model.title)}</span>
2667
+ <strong class="absolute-voice-delivery-runtime__label">${escapeHtml3(model.label)}</strong>
2391
2668
  </header>
2392
- <p class="absolute-voice-delivery-runtime__description">${escapeHtml2(model.description)}</p>
2669
+ <p class="absolute-voice-delivery-runtime__description">${escapeHtml3(model.description)}</p>
2393
2670
  <ul class="absolute-voice-delivery-runtime__surfaces">${surfaces}</ul>
2394
2671
  ${actions}
2395
2672
  ${actionError}
2396
- ${model.error ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml2(model.error)}</p>` : ""}
2673
+ ${model.error ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml3(model.error)}</p>` : ""}
2397
2674
  </section>`;
2398
2675
  };
2399
2676
  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}`;
@@ -2529,9 +2806,9 @@ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {})
2529
2806
  };
2530
2807
  };
2531
2808
  // src/client/routingStatusWidget.ts
2532
- var DEFAULT_TITLE3 = "Voice Routing";
2533
- var DEFAULT_DESCRIPTION3 = "Latest provider routing decision from the self-hosted trace store.";
2534
- var escapeHtml3 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2809
+ var DEFAULT_TITLE4 = "Voice Routing";
2810
+ 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;");
2535
2812
  var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
2536
2813
  var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
2537
2814
  const decision = snapshot.decision;
@@ -2555,30 +2832,30 @@ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
2555
2832
  ] : [];
2556
2833
  return {
2557
2834
  decision,
2558
- description: options.description ?? DEFAULT_DESCRIPTION3,
2835
+ description: options.description ?? DEFAULT_DESCRIPTION4,
2559
2836
  error: snapshot.error,
2560
2837
  isLoading: snapshot.isLoading,
2561
2838
  label: snapshot.error ? "Unavailable" : decision ? `${formatValue(decision.kind).toUpperCase()} ${formatValue(decision.status, "unknown")}` : snapshot.isLoading ? "Checking" : "No routing yet",
2562
2839
  rows,
2563
2840
  status: snapshot.error ? "error" : decision ? "ready" : snapshot.isLoading ? "loading" : "empty",
2564
- title: options.title ?? DEFAULT_TITLE3,
2841
+ title: options.title ?? DEFAULT_TITLE4,
2565
2842
  updatedAt: snapshot.updatedAt
2566
2843
  };
2567
2844
  };
2568
2845
  var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
2569
2846
  const model = createVoiceRoutingStatusViewModel(snapshot, options);
2570
2847
  const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
2571
- <span>${escapeHtml3(row.label)}</span>
2572
- <strong>${escapeHtml3(row.value)}</strong>
2848
+ <span>${escapeHtml4(row.label)}</span>
2849
+ <strong>${escapeHtml4(row.value)}</strong>
2573
2850
  </div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
2574
- return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml3(model.status)}">
2851
+ return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml4(model.status)}">
2575
2852
  <header class="absolute-voice-routing-status__header">
2576
- <span class="absolute-voice-routing-status__eyebrow">${escapeHtml3(model.title)}</span>
2577
- <strong class="absolute-voice-routing-status__label">${escapeHtml3(model.label)}</strong>
2853
+ <span class="absolute-voice-routing-status__eyebrow">${escapeHtml4(model.title)}</span>
2854
+ <strong class="absolute-voice-routing-status__label">${escapeHtml4(model.label)}</strong>
2578
2855
  </header>
2579
- <p class="absolute-voice-routing-status__description">${escapeHtml3(model.description)}</p>
2856
+ <p class="absolute-voice-routing-status__description">${escapeHtml4(model.description)}</p>
2580
2857
  ${rows}
2581
- ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml3(model.error)}</p>` : ""}
2858
+ ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml4(model.error)}</p>` : ""}
2582
2859
  </section>`;
2583
2860
  };
2584
2861
  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}`;
@@ -3230,7 +3507,7 @@ var createVoiceProviderSimulationControlsStore = (options) => {
3230
3507
  };
3231
3508
  };
3232
3509
  // src/client/providerSimulationControlsWidget.ts
3233
- var escapeHtml4 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3510
+ var escapeHtml5 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3234
3511
  var formatKind = (kind) => (kind ?? "stt").toUpperCase();
3235
3512
  var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
3236
3513
  const configuredProviders = options.providers.filter((provider) => provider.configured !== false);
@@ -3250,18 +3527,18 @@ var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
3250
3527
  };
3251
3528
  var renderVoiceProviderSimulationControlsHTML = (snapshot, options) => {
3252
3529
  const model = createVoiceProviderSimulationControlsViewModel(snapshot, options);
3253
- const failureButtons = model.failureProviders.map((provider) => `<button type="button" data-voice-provider-fail="${escapeHtml4(provider.provider)}"${!model.canSimulateFailure || snapshot.isRunning ? " disabled" : ""}>Simulate ${escapeHtml4(provider.provider)} ${escapeHtml4(formatKind(options.kind))} failure</button>`).join("");
3254
- const recoveryButtons = model.providers.map((provider) => `<button type="button" data-voice-provider-recover="${escapeHtml4(provider.provider)}"${snapshot.isRunning ? " disabled" : ""}>Mark ${escapeHtml4(provider.provider)} recovered</button>`).join("");
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("");
3255
3532
  return `<section class="absolute-voice-provider-simulation absolute-voice-provider-simulation--${snapshot.error ? "error" : snapshot.isRunning ? "running" : "ready"}">
3256
3533
  <header class="absolute-voice-provider-simulation__header">
3257
- <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml4(model.title)}</span>
3258
- <strong class="absolute-voice-provider-simulation__label">${escapeHtml4(model.label)}</strong>
3534
+ <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml5(model.title)}</span>
3535
+ <strong class="absolute-voice-provider-simulation__label">${escapeHtml5(model.label)}</strong>
3259
3536
  </header>
3260
- <p class="absolute-voice-provider-simulation__description">${escapeHtml4(model.description)}</p>
3261
- ${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml4(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
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>`}
3262
3539
  <div class="absolute-voice-provider-simulation__actions">${failureButtons}${recoveryButtons}</div>
3263
- ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml4(snapshot.error)}</p>` : ""}
3264
- ${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml4(model.resultText)}</pre>` : ""}
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>` : ""}
3265
3542
  </section>`;
3266
3543
  };
3267
3544
  var bindVoiceProviderSimulationControls = (element, store) => {
@@ -3326,9 +3603,9 @@ var defineVoiceProviderSimulationControlsElement = (tagName = "absolute-voice-pr
3326
3603
  });
3327
3604
  };
3328
3605
  // src/client/providerStatusWidget.ts
3329
- var DEFAULT_TITLE4 = "Voice Providers";
3330
- var DEFAULT_DESCRIPTION4 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
3331
- var escapeHtml5 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3606
+ var DEFAULT_TITLE5 = "Voice Providers";
3607
+ 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;");
3332
3609
  var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
3333
3610
  var formatStatus = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
3334
3611
  var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
@@ -3372,37 +3649,37 @@ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
3372
3649
  const warningCount = providers.filter((provider) => isWarningStatus(provider.status)).length;
3373
3650
  const healthyCount = providers.filter((provider) => provider.status === "healthy").length;
3374
3651
  return {
3375
- description: options.description ?? DEFAULT_DESCRIPTION4,
3652
+ description: options.description ?? DEFAULT_DESCRIPTION5,
3376
3653
  error: snapshot.error,
3377
3654
  isLoading: snapshot.isLoading,
3378
3655
  label: snapshot.error ? "Unavailable" : providers.length ? warningCount > 0 ? `${warningCount} needs attention` : `${healthyCount} healthy` : snapshot.isLoading ? "Checking" : "No provider traffic",
3379
3656
  providers,
3380
3657
  status: snapshot.error ? "error" : providers.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
3381
- title: options.title ?? DEFAULT_TITLE4,
3658
+ title: options.title ?? DEFAULT_TITLE5,
3382
3659
  updatedAt: snapshot.updatedAt
3383
3660
  };
3384
3661
  };
3385
3662
  var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
3386
3663
  const model = createVoiceProviderStatusViewModel(snapshot, options);
3387
- 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--${escapeHtml5(provider.status)}">
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)}">
3388
3665
  <header>
3389
- <strong>${escapeHtml5(provider.label)}</strong>
3390
- <span>${escapeHtml5(formatStatus(provider.status))}</span>
3666
+ <strong>${escapeHtml6(provider.label)}</strong>
3667
+ <span>${escapeHtml6(formatStatus(provider.status))}</span>
3391
3668
  </header>
3392
- <p>${escapeHtml5(provider.detail)}</p>
3669
+ <p>${escapeHtml6(provider.detail)}</p>
3393
3670
  <dl>${provider.rows.map((row) => `<div>
3394
- <dt>${escapeHtml5(row.label)}</dt>
3395
- <dd>${escapeHtml5(row.value)}</dd>
3671
+ <dt>${escapeHtml6(row.label)}</dt>
3672
+ <dd>${escapeHtml6(row.value)}</dd>
3396
3673
  </div>`).join("")}</dl>
3397
3674
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
3398
- return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml5(model.status)}">
3675
+ return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml6(model.status)}">
3399
3676
  <header class="absolute-voice-provider-status__header">
3400
- <span class="absolute-voice-provider-status__eyebrow">${escapeHtml5(model.title)}</span>
3401
- <strong class="absolute-voice-provider-status__label">${escapeHtml5(model.label)}</strong>
3677
+ <span class="absolute-voice-provider-status__eyebrow">${escapeHtml6(model.title)}</span>
3678
+ <strong class="absolute-voice-provider-status__label">${escapeHtml6(model.label)}</strong>
3402
3679
  </header>
3403
- <p class="absolute-voice-provider-status__description">${escapeHtml5(model.description)}</p>
3680
+ <p class="absolute-voice-provider-status__description">${escapeHtml6(model.description)}</p>
3404
3681
  ${providers}
3405
- ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml5(model.error)}</p>` : ""}
3682
+ ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml6(model.error)}</p>` : ""}
3406
3683
  </section>`;
3407
3684
  };
3408
3685
  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}`;
@@ -3443,9 +3720,9 @@ var defineVoiceProviderStatusElement = (tagName = "absolute-voice-provider-statu
3443
3720
  });
3444
3721
  };
3445
3722
  // src/client/providerCapabilitiesWidget.ts
3446
- var DEFAULT_TITLE5 = "Provider Capabilities";
3447
- var DEFAULT_DESCRIPTION5 = "Configured, selected, and healthy voice providers for this deployment.";
3448
- var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3723
+ var DEFAULT_TITLE6 = "Provider Capabilities";
3724
+ 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;");
3449
3726
  var formatProvider2 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
3450
3727
  var formatKind2 = (kind) => kind.toUpperCase();
3451
3728
  var formatStatus2 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
@@ -3489,36 +3766,36 @@ var createVoiceProviderCapabilitiesViewModel = (snapshot, options = {}) => {
3489
3766
  const selectedCount = snapshot.report?.selected ?? capabilities.filter((capability) => capability.selected).length;
3490
3767
  return {
3491
3768
  capabilities,
3492
- description: options.description ?? DEFAULT_DESCRIPTION5,
3769
+ description: options.description ?? DEFAULT_DESCRIPTION6,
3493
3770
  error: snapshot.error,
3494
3771
  isLoading: snapshot.isLoading,
3495
3772
  label: snapshot.error ? "Unavailable" : capabilities.length ? warningCount > 0 ? `${warningCount} needs attention` : `${selectedCount} selected` : snapshot.isLoading ? "Checking" : "No capabilities",
3496
3773
  status: snapshot.error ? "error" : capabilities.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
3497
- title: options.title ?? DEFAULT_TITLE5,
3774
+ title: options.title ?? DEFAULT_TITLE6,
3498
3775
  updatedAt: snapshot.updatedAt
3499
3776
  };
3500
3777
  };
3501
3778
  var renderVoiceProviderCapabilitiesHTML = (snapshot, options = {}) => {
3502
3779
  const model = createVoiceProviderCapabilitiesViewModel(snapshot, options);
3503
- 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--${escapeHtml6(capability.status)}">
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)}">
3504
3781
  <header>
3505
- <strong>${escapeHtml6(capability.label)}</strong>
3506
- <span>${escapeHtml6(formatStatus2(capability.status))}</span>
3782
+ <strong>${escapeHtml7(capability.label)}</strong>
3783
+ <span>${escapeHtml7(formatStatus2(capability.status))}</span>
3507
3784
  </header>
3508
- <p>${escapeHtml6(capability.detail)}</p>
3785
+ <p>${escapeHtml7(capability.detail)}</p>
3509
3786
  <dl>${capability.rows.map((row) => `<div>
3510
- <dt>${escapeHtml6(row.label)}</dt>
3511
- <dd>${escapeHtml6(row.value)}</dd>
3787
+ <dt>${escapeHtml7(row.label)}</dt>
3788
+ <dd>${escapeHtml7(row.value)}</dd>
3512
3789
  </div>`).join("")}</dl>
3513
3790
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-capabilities__empty">Configure provider capabilities to see deployment coverage.</p>';
3514
- return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml6(model.status)}">
3791
+ return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml7(model.status)}">
3515
3792
  <header class="absolute-voice-provider-capabilities__header">
3516
- <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml6(model.title)}</span>
3517
- <strong class="absolute-voice-provider-capabilities__label">${escapeHtml6(model.label)}</strong>
3793
+ <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml7(model.title)}</span>
3794
+ <strong class="absolute-voice-provider-capabilities__label">${escapeHtml7(model.label)}</strong>
3518
3795
  </header>
3519
- <p class="absolute-voice-provider-capabilities__description">${escapeHtml6(model.description)}</p>
3796
+ <p class="absolute-voice-provider-capabilities__description">${escapeHtml7(model.description)}</p>
3520
3797
  ${capabilities}
3521
- ${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml6(model.error)}</p>` : ""}
3798
+ ${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml7(model.error)}</p>` : ""}
3522
3799
  </section>`;
3523
3800
  };
3524
3801
  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}`;
@@ -3559,9 +3836,9 @@ var defineVoiceProviderCapabilitiesElement = (tagName = "absolute-voice-provider
3559
3836
  });
3560
3837
  };
3561
3838
  // src/client/turnQualityWidget.ts
3562
- var DEFAULT_TITLE6 = "Turn Quality";
3563
- var DEFAULT_DESCRIPTION6 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
3564
- var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3839
+ var DEFAULT_TITLE7 = "Turn Quality";
3840
+ 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;");
3565
3842
  var formatConfidence = (value) => typeof value === "number" ? `${Math.round(value * 100)}%` : "n/a";
3566
3843
  var formatMaybe = (value) => value === undefined || value === "" ? "n/a" : String(value);
3567
3844
  var getTurnDetail = (turn) => {
@@ -3599,37 +3876,37 @@ var createVoiceTurnQualityViewModel = (snapshot, options = {}) => {
3599
3876
  const warningCount = snapshot.report?.warnings ?? turns.filter((turn) => turn.status === "warn").length;
3600
3877
  const failedCount = snapshot.report?.failed ?? turns.filter((turn) => turn.status === "fail").length;
3601
3878
  return {
3602
- description: options.description ?? DEFAULT_DESCRIPTION6,
3879
+ description: options.description ?? DEFAULT_DESCRIPTION7,
3603
3880
  error: snapshot.error,
3604
3881
  isLoading: snapshot.isLoading,
3605
3882
  label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} failed` : warningCount > 0 ? `${warningCount} warnings` : `${turns.length} healthy` : snapshot.isLoading ? "Checking" : "No turns",
3606
3883
  status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
3607
- title: options.title ?? DEFAULT_TITLE6,
3884
+ title: options.title ?? DEFAULT_TITLE7,
3608
3885
  turns,
3609
3886
  updatedAt: snapshot.updatedAt
3610
3887
  };
3611
3888
  };
3612
3889
  var renderVoiceTurnQualityHTML = (snapshot, options = {}) => {
3613
3890
  const model = createVoiceTurnQualityViewModel(snapshot, options);
3614
- 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--${escapeHtml7(turn.status)}">
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)}">
3615
3892
  <header>
3616
- <strong>${escapeHtml7(turn.label)}</strong>
3617
- <span>${escapeHtml7(turn.status)}</span>
3893
+ <strong>${escapeHtml8(turn.label)}</strong>
3894
+ <span>${escapeHtml8(turn.status)}</span>
3618
3895
  </header>
3619
- <p>${escapeHtml7(turn.detail)}</p>
3896
+ <p>${escapeHtml8(turn.detail)}</p>
3620
3897
  <dl>${turn.rows.map((row) => `<div>
3621
- <dt>${escapeHtml7(row.label)}</dt>
3622
- <dd>${escapeHtml7(row.value)}</dd>
3898
+ <dt>${escapeHtml8(row.label)}</dt>
3899
+ <dd>${escapeHtml8(row.value)}</dd>
3623
3900
  </div>`).join("")}</dl>
3624
3901
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-quality__empty">Complete a voice turn to see STT quality diagnostics.</p>';
3625
- return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml7(model.status)}">
3902
+ return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml8(model.status)}">
3626
3903
  <header class="absolute-voice-turn-quality__header">
3627
- <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml7(model.title)}</span>
3628
- <strong class="absolute-voice-turn-quality__label">${escapeHtml7(model.label)}</strong>
3904
+ <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml8(model.title)}</span>
3905
+ <strong class="absolute-voice-turn-quality__label">${escapeHtml8(model.label)}</strong>
3629
3906
  </header>
3630
- <p class="absolute-voice-turn-quality__description">${escapeHtml7(model.description)}</p>
3907
+ <p class="absolute-voice-turn-quality__description">${escapeHtml8(model.description)}</p>
3631
3908
  ${turns}
3632
- ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml7(model.error)}</p>` : ""}
3909
+ ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml8(model.error)}</p>` : ""}
3633
3910
  </section>`;
3634
3911
  };
3635
3912
  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}`;
@@ -3670,10 +3947,10 @@ var defineVoiceTurnQualityElement = (tagName = "absolute-voice-turn-quality") =>
3670
3947
  });
3671
3948
  };
3672
3949
  // src/client/turnLatencyWidget.ts
3673
- var DEFAULT_TITLE7 = "Turn Latency";
3674
- var DEFAULT_DESCRIPTION7 = "Per-turn timing from first transcript to commit and assistant response start.";
3950
+ var DEFAULT_TITLE8 = "Turn Latency";
3951
+ var DEFAULT_DESCRIPTION8 = "Per-turn timing from first transcript to commit and assistant response start.";
3675
3952
  var DEFAULT_PROOF_LABEL = "Run latency proof";
3676
- var escapeHtml8 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3953
+ var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3677
3954
  var formatMs = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
3678
3955
  var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
3679
3956
  const turns = (snapshot.report?.turns ?? []).map((turn) => ({
@@ -3687,39 +3964,39 @@ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
3687
3964
  const warningCount = snapshot.report?.warnings ?? turns.filter((turn) => turn.status === "warn").length;
3688
3965
  const failedCount = snapshot.report?.failed ?? turns.filter((turn) => turn.status === "fail").length;
3689
3966
  return {
3690
- description: options.description ?? DEFAULT_DESCRIPTION7,
3967
+ description: options.description ?? DEFAULT_DESCRIPTION8,
3691
3968
  error: snapshot.error,
3692
3969
  isLoading: snapshot.isLoading,
3693
3970
  label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} slow` : warningCount > 0 ? `${warningCount} warnings` : `avg ${formatMs(snapshot.report?.averageTotalMs)}` : snapshot.isLoading ? "Checking" : "No turns",
3694
3971
  proofLabel: options.proofPath ? options.proofLabel ?? DEFAULT_PROOF_LABEL : undefined,
3695
3972
  showProofAction: Boolean(options.proofPath),
3696
3973
  status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
3697
- title: options.title ?? DEFAULT_TITLE7,
3974
+ title: options.title ?? DEFAULT_TITLE8,
3698
3975
  turns,
3699
3976
  updatedAt: snapshot.updatedAt
3700
3977
  };
3701
3978
  };
3702
3979
  var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
3703
3980
  const model = createVoiceTurnLatencyViewModel(snapshot, options);
3704
- 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--${escapeHtml8(turn.status)}">
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)}">
3705
3982
  <header>
3706
- <strong>${escapeHtml8(turn.label)}</strong>
3707
- <span>${escapeHtml8(turn.status)}</span>
3983
+ <strong>${escapeHtml9(turn.label)}</strong>
3984
+ <span>${escapeHtml9(turn.status)}</span>
3708
3985
  </header>
3709
3986
  <dl>${turn.rows.map((row) => `<div>
3710
- <dt>${escapeHtml8(row.label)}</dt>
3711
- <dd>${escapeHtml8(row.value)}</dd>
3987
+ <dt>${escapeHtml9(row.label)}</dt>
3988
+ <dd>${escapeHtml9(row.value)}</dd>
3712
3989
  </div>`).join("")}</dl>
3713
3990
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-latency__empty">Complete a voice turn to see latency diagnostics.</p>';
3714
- return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml8(model.status)}">
3991
+ return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml9(model.status)}">
3715
3992
  <header class="absolute-voice-turn-latency__header">
3716
- <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml8(model.title)}</span>
3717
- <strong class="absolute-voice-turn-latency__label">${escapeHtml8(model.label)}</strong>
3993
+ <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml9(model.title)}</span>
3994
+ <strong class="absolute-voice-turn-latency__label">${escapeHtml9(model.label)}</strong>
3718
3995
  </header>
3719
- <p class="absolute-voice-turn-latency__description">${escapeHtml8(model.description)}</p>
3720
- ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml8(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
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>` : ""}
3721
3998
  ${turns}
3722
- ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml8(model.error)}</p>` : ""}
3999
+ ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml9(model.error)}</p>` : ""}
3723
4000
  </section>`;
3724
4001
  };
3725
4002
  var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {}) => {
@@ -3769,9 +4046,9 @@ var defineVoiceTurnLatencyElement = (tagName = "absolute-voice-turn-latency") =>
3769
4046
  });
3770
4047
  };
3771
4048
  // src/client/traceTimelineWidget.ts
3772
- var DEFAULT_TITLE8 = "Voice Traces";
3773
- var DEFAULT_DESCRIPTION8 = "Latest call timelines with provider latency, fallbacks, handoffs, and errors from your self-hosted trace store.";
3774
- var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4049
+ var DEFAULT_TITLE9 = "Voice Traces";
4050
+ 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;");
3775
4052
  var formatMs2 = (value) => typeof value === "number" ? `${value}ms` : "n/a";
3776
4053
  var formatProviders = (session) => session.providers.length ? session.providers.map((provider) => provider.provider).join(", ") : "No providers";
3777
4054
  var createVoiceTraceTimelineViewModel = (snapshot, options = {}) => {
@@ -3785,34 +4062,34 @@ var createVoiceTraceTimelineViewModel = (snapshot, options = {}) => {
3785
4062
  const failed = sessions.filter((session) => session.status === "failed").length;
3786
4063
  const warnings = sessions.filter((session) => session.status === "warning").length;
3787
4064
  return {
3788
- description: options.description ?? DEFAULT_DESCRIPTION8,
4065
+ description: options.description ?? DEFAULT_DESCRIPTION9,
3789
4066
  error: snapshot.error,
3790
4067
  isLoading: snapshot.isLoading,
3791
4068
  label: snapshot.error ? "Unavailable" : failed > 0 ? `${failed} failed` : warnings > 0 ? `${warnings} warning` : sessions.length ? `${sessions.length} recent` : snapshot.isLoading ? "Checking" : "No traces yet",
3792
4069
  sessions,
3793
4070
  status: snapshot.error ? "error" : failed > 0 ? "failed" : warnings > 0 ? "warning" : sessions.length ? "ready" : snapshot.isLoading ? "loading" : "empty",
3794
- title: options.title ?? DEFAULT_TITLE8,
4071
+ title: options.title ?? DEFAULT_TITLE9,
3795
4072
  updatedAt: snapshot.updatedAt
3796
4073
  };
3797
4074
  };
3798
4075
  var renderVoiceTraceTimelineWidgetHTML = (snapshot, options = {}) => {
3799
4076
  const model = createVoiceTraceTimelineViewModel(snapshot, options);
3800
- 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--${escapeHtml9(session.status)}">
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)}">
3801
4078
  <header>
3802
- <strong>${escapeHtml9(session.sessionId)}</strong>
3803
- <span>${escapeHtml9(session.status)}</span>
4079
+ <strong>${escapeHtml10(session.sessionId)}</strong>
4080
+ <span>${escapeHtml10(session.status)}</span>
3804
4081
  </header>
3805
- <p>${escapeHtml9(session.label)} \xB7 ${escapeHtml9(session.durationLabel)} \xB7 ${escapeHtml9(session.providerLabel)}</p>
3806
- <a href="${escapeHtml9(session.detailHref)}">Open timeline</a>
4082
+ <p>${escapeHtml10(session.label)} \xB7 ${escapeHtml10(session.durationLabel)} \xB7 ${escapeHtml10(session.providerLabel)}</p>
4083
+ <a href="${escapeHtml10(session.detailHref)}">Open timeline</a>
3807
4084
  </article>`).join("")}</div>` : '<p class="absolute-voice-trace-timeline__empty">Run a voice session to see call timelines.</p>';
3808
- return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml9(model.status)}">
4085
+ return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml10(model.status)}">
3809
4086
  <header class="absolute-voice-trace-timeline__header">
3810
- <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml9(model.title)}</span>
3811
- <strong class="absolute-voice-trace-timeline__label">${escapeHtml9(model.label)}</strong>
4087
+ <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml10(model.title)}</span>
4088
+ <strong class="absolute-voice-trace-timeline__label">${escapeHtml10(model.label)}</strong>
3812
4089
  </header>
3813
- <p class="absolute-voice-trace-timeline__description">${escapeHtml9(model.description)}</p>
4090
+ <p class="absolute-voice-trace-timeline__description">${escapeHtml10(model.description)}</p>
3814
4091
  ${sessions}
3815
- ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml9(model.error)}</p>` : ""}
4092
+ ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml10(model.error)}</p>` : ""}
3816
4093
  </section>`;
3817
4094
  };
3818
4095
  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}`;
@@ -3935,6 +4212,7 @@ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options =
3935
4212
  };
3936
4213
  export {
3937
4214
  runVoiceTurnLatencyProof,
4215
+ runVoiceOpsAction,
3938
4216
  runVoiceDeliveryRuntimeAction,
3939
4217
  runVoiceCampaignDialerProofAction,
3940
4218
  renderVoiceTurnQualityHTML,
@@ -3945,7 +4223,9 @@ export {
3945
4223
  renderVoiceProviderSimulationControlsHTML,
3946
4224
  renderVoiceProviderCapabilitiesHTML,
3947
4225
  renderVoiceOpsStatusHTML,
4226
+ renderVoiceOpsActionCenterHTML,
3948
4227
  renderVoiceDeliveryRuntimeHTML,
4228
+ recordVoiceOpsActionResult,
3949
4229
  mountVoiceTurnQuality,
3950
4230
  mountVoiceTurnLatency,
3951
4231
  mountVoiceTraceTimeline,
@@ -3954,6 +4234,7 @@ export {
3954
4234
  mountVoiceProviderSimulationControls,
3955
4235
  mountVoiceProviderCapabilities,
3956
4236
  mountVoiceOpsStatus,
4237
+ mountVoiceOpsActionCenter,
3957
4238
  mountVoiceDeliveryRuntime,
3958
4239
  getVoiceTurnQualityCSS,
3959
4240
  getVoiceTraceTimelineCSS,
@@ -3962,6 +4243,7 @@ export {
3962
4243
  getVoiceProviderCapabilitiesCSS,
3963
4244
  getVoiceOpsStatusLabel,
3964
4245
  getVoiceOpsStatusCSS,
4246
+ getVoiceOpsActionCenterCSS,
3965
4247
  getVoiceDeliveryRuntimeCSS,
3966
4248
  fetchVoiceWorkflowStatus,
3967
4249
  fetchVoiceTurnQuality,
@@ -3981,6 +4263,7 @@ export {
3981
4263
  defineVoiceProviderSimulationControlsElement,
3982
4264
  defineVoiceProviderCapabilitiesElement,
3983
4265
  defineVoiceOpsStatusElement,
4266
+ defineVoiceOpsActionCenterElement,
3984
4267
  defineVoiceDeliveryRuntimeElement,
3985
4268
  decodeVoiceAudioChunk,
3986
4269
  createVoiceWorkflowStatusStore,
@@ -4001,6 +4284,9 @@ export {
4001
4284
  createVoiceProviderCapabilitiesStore,
4002
4285
  createVoiceOpsStatusViewModel,
4003
4286
  createVoiceOpsStatusStore,
4287
+ createVoiceOpsActionCenterViewModel,
4288
+ createVoiceOpsActionCenterStore,
4289
+ createVoiceOpsActionCenterActions,
4004
4290
  createVoiceLiveTurnLatencyMonitor,
4005
4291
  createVoiceDuplexController,
4006
4292
  createVoiceDeliveryRuntimeViewModel,