@absolutejs/voice 0.0.22-beta.18 → 0.0.22-beta.180

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.
Files changed (189) hide show
  1. package/README.md +1097 -5
  2. package/dist/agent.d.ts +24 -0
  3. package/dist/agentSquadContract.d.ts +64 -0
  4. package/dist/angular/index.d.ts +12 -0
  5. package/dist/angular/index.js +3196 -1111
  6. package/dist/angular/voice-campaign-dialer-proof.service.d.ts +14 -0
  7. package/dist/angular/voice-controller.service.d.ts +1 -0
  8. package/dist/angular/voice-delivery-runtime.component.d.ts +17 -0
  9. package/dist/angular/voice-delivery-runtime.service.d.ts +16 -0
  10. package/dist/angular/voice-ops-action-center.service.d.ts +13 -0
  11. package/dist/angular/voice-ops-status.component.d.ts +15 -0
  12. package/dist/angular/voice-ops-status.service.d.ts +12 -0
  13. package/dist/angular/voice-provider-capabilities.service.d.ts +12 -0
  14. package/dist/angular/voice-provider-contracts.service.d.ts +12 -0
  15. package/dist/angular/voice-routing-status.service.d.ts +11 -0
  16. package/dist/angular/voice-stream.service.d.ts +3 -0
  17. package/dist/angular/voice-trace-timeline.service.d.ts +12 -0
  18. package/dist/angular/voice-turn-latency.service.d.ts +13 -0
  19. package/dist/angular/voice-turn-quality.service.d.ts +12 -0
  20. package/dist/angular/voice-workflow-status.service.d.ts +12 -0
  21. package/dist/assistantHealth.d.ts +2 -0
  22. package/dist/audit.d.ts +128 -0
  23. package/dist/auditDeliveryRoutes.d.ts +85 -0
  24. package/dist/auditExport.d.ts +34 -0
  25. package/dist/auditRoutes.d.ts +66 -0
  26. package/dist/auditSinks.d.ts +151 -0
  27. package/dist/bargeInRoutes.d.ts +56 -0
  28. package/dist/campaign.d.ts +610 -0
  29. package/dist/campaignDialers.d.ts +90 -0
  30. package/dist/client/actions.d.ts +105 -0
  31. package/dist/client/bargeInMonitor.d.ts +7 -0
  32. package/dist/client/campaignDialerProof.d.ts +23 -0
  33. package/dist/client/connection.d.ts +3 -0
  34. package/dist/client/deliveryRuntime.d.ts +34 -0
  35. package/dist/client/deliveryRuntimeWidget.d.ts +37 -0
  36. package/dist/client/duplex.d.ts +1 -1
  37. package/dist/client/htmxBootstrap.js +747 -15
  38. package/dist/client/index.d.ts +54 -0
  39. package/dist/client/index.js +2944 -20
  40. package/dist/client/liveTurnLatency.d.ts +41 -0
  41. package/dist/client/opsActionCenter.d.ts +54 -0
  42. package/dist/client/opsActionCenterWidget.d.ts +29 -0
  43. package/dist/client/opsActionHistory.d.ts +19 -0
  44. package/dist/client/opsActionHistoryWidget.d.ts +11 -0
  45. package/dist/client/opsStatus.d.ts +19 -0
  46. package/dist/client/opsStatusWidget.d.ts +40 -0
  47. package/dist/client/providerCapabilities.d.ts +19 -0
  48. package/dist/client/providerCapabilitiesWidget.d.ts +32 -0
  49. package/dist/client/providerContracts.d.ts +19 -0
  50. package/dist/client/providerContractsWidget.d.ts +37 -0
  51. package/dist/client/providerSimulationControls.d.ts +33 -0
  52. package/dist/client/providerSimulationControlsWidget.d.ts +20 -0
  53. package/dist/client/providerStatusWidget.d.ts +32 -0
  54. package/dist/client/routingStatus.d.ts +19 -0
  55. package/dist/client/routingStatusWidget.d.ts +28 -0
  56. package/dist/client/traceTimeline.d.ts +19 -0
  57. package/dist/client/traceTimelineWidget.d.ts +32 -0
  58. package/dist/client/turnLatency.d.ts +22 -0
  59. package/dist/client/turnLatencyWidget.d.ts +33 -0
  60. package/dist/client/turnQuality.d.ts +19 -0
  61. package/dist/client/turnQualityWidget.d.ts +32 -0
  62. package/dist/client/workflowStatus.d.ts +19 -0
  63. package/dist/dataControl.d.ts +47 -0
  64. package/dist/deliveryRuntime.d.ts +158 -0
  65. package/dist/deliverySinkRoutes.d.ts +117 -0
  66. package/dist/demoReadyRoutes.d.ts +98 -0
  67. package/dist/diagnosticsRoutes.d.ts +44 -0
  68. package/dist/evalRoutes.d.ts +213 -0
  69. package/dist/fileStore.d.ts +11 -2
  70. package/dist/handoff.d.ts +54 -0
  71. package/dist/handoffHealth.d.ts +94 -0
  72. package/dist/index.d.ts +112 -11
  73. package/dist/index.js +19259 -4988
  74. package/dist/liveLatency.d.ts +78 -0
  75. package/dist/modelAdapters.d.ts +23 -2
  76. package/dist/openaiRealtime.d.ts +27 -0
  77. package/dist/openaiTTS.d.ts +18 -0
  78. package/dist/opsActionAuditRoutes.d.ts +99 -0
  79. package/dist/opsConsoleRoutes.d.ts +80 -0
  80. package/dist/opsStatus.d.ts +76 -0
  81. package/dist/opsStatusRoutes.d.ts +33 -0
  82. package/dist/opsWebhook.d.ts +126 -0
  83. package/dist/outcomeContract.d.ts +112 -0
  84. package/dist/phoneAgent.d.ts +62 -0
  85. package/dist/phoneAgentProductionSmoke.d.ts +115 -0
  86. package/dist/postgresStore.d.ts +13 -2
  87. package/dist/productionReadiness.d.ts +369 -0
  88. package/dist/providerAdapters.d.ts +48 -0
  89. package/dist/providerCapabilities.d.ts +92 -0
  90. package/dist/providerHealth.d.ts +1 -0
  91. package/dist/providerRoutingContract.d.ts +38 -0
  92. package/dist/providerStackRecommendations.d.ts +145 -0
  93. package/dist/qualityRoutes.d.ts +76 -0
  94. package/dist/queue.d.ts +61 -0
  95. package/dist/react/VoiceDeliveryRuntime.d.ts +7 -0
  96. package/dist/react/VoiceOpsActionCenter.d.ts +5 -0
  97. package/dist/react/VoiceOpsStatus.d.ts +6 -0
  98. package/dist/react/VoiceProviderCapabilities.d.ts +6 -0
  99. package/dist/react/VoiceProviderContracts.d.ts +6 -0
  100. package/dist/react/VoiceProviderSimulationControls.d.ts +5 -0
  101. package/dist/react/VoiceProviderStatus.d.ts +6 -0
  102. package/dist/react/VoiceRoutingStatus.d.ts +6 -0
  103. package/dist/react/VoiceTraceTimeline.d.ts +6 -0
  104. package/dist/react/VoiceTurnLatency.d.ts +6 -0
  105. package/dist/react/VoiceTurnQuality.d.ts +6 -0
  106. package/dist/react/index.d.ts +23 -0
  107. package/dist/react/index.js +3695 -33
  108. package/dist/react/useVoiceCampaignDialerProof.d.ts +10 -0
  109. package/dist/react/useVoiceController.d.ts +3 -0
  110. package/dist/react/useVoiceDeliveryRuntime.d.ts +13 -0
  111. package/dist/react/useVoiceOpsActionCenter.d.ts +11 -0
  112. package/dist/react/useVoiceOpsStatus.d.ts +8 -0
  113. package/dist/react/useVoiceProviderCapabilities.d.ts +8 -0
  114. package/dist/react/useVoiceProviderContracts.d.ts +8 -0
  115. package/dist/react/useVoiceProviderSimulationControls.d.ts +10 -0
  116. package/dist/react/useVoiceRoutingStatus.d.ts +8 -0
  117. package/dist/react/useVoiceStream.d.ts +3 -0
  118. package/dist/react/useVoiceTraceTimeline.d.ts +8 -0
  119. package/dist/react/useVoiceTurnLatency.d.ts +9 -0
  120. package/dist/react/useVoiceTurnQuality.d.ts +8 -0
  121. package/dist/react/useVoiceWorkflowStatus.d.ts +8 -0
  122. package/dist/readinessProfiles.d.ts +35 -0
  123. package/dist/reconnectContract.d.ts +87 -0
  124. package/dist/resilienceRoutes.d.ts +142 -0
  125. package/dist/sessionReplay.d.ts +185 -0
  126. package/dist/simulationSuite.d.ts +120 -0
  127. package/dist/sqliteStore.d.ts +13 -2
  128. package/dist/svelte/createVoiceCampaignDialerProof.d.ts +9 -0
  129. package/dist/svelte/createVoiceDeliveryRuntime.d.ts +11 -0
  130. package/dist/svelte/createVoiceOpsActionCenter.d.ts +10 -0
  131. package/dist/svelte/createVoiceOpsStatus.d.ts +9 -0
  132. package/dist/svelte/createVoiceProviderCapabilities.d.ts +10 -0
  133. package/dist/svelte/createVoiceProviderContracts.d.ts +10 -0
  134. package/dist/svelte/createVoiceProviderSimulationControls.d.ts +11 -0
  135. package/dist/svelte/createVoiceProviderStatus.d.ts +4 -2
  136. package/dist/svelte/createVoiceRoutingStatus.d.ts +10 -0
  137. package/dist/svelte/createVoiceTraceTimeline.d.ts +10 -0
  138. package/dist/svelte/createVoiceTurnLatency.d.ts +11 -0
  139. package/dist/svelte/createVoiceTurnQuality.d.ts +10 -0
  140. package/dist/svelte/createVoiceWorkflowStatus.d.ts +8 -0
  141. package/dist/svelte/index.d.ts +12 -0
  142. package/dist/svelte/index.js +3095 -439
  143. package/dist/telephony/contract.d.ts +61 -0
  144. package/dist/telephony/matrix.d.ts +97 -0
  145. package/dist/telephony/plivo.d.ts +254 -0
  146. package/dist/telephony/telnyx.d.ts +247 -0
  147. package/dist/telephony/twilio.d.ts +135 -2
  148. package/dist/telephonyOutcome.d.ts +201 -0
  149. package/dist/testing/index.d.ts +1 -0
  150. package/dist/testing/index.js +2008 -69
  151. package/dist/testing/ioProviderSimulator.d.ts +41 -0
  152. package/dist/testing/providerSimulator.d.ts +8 -0
  153. package/dist/toolContract.d.ts +130 -0
  154. package/dist/toolRuntime.d.ts +50 -0
  155. package/dist/trace.d.ts +19 -1
  156. package/dist/traceDeliveryRoutes.d.ts +86 -0
  157. package/dist/traceTimeline.d.ts +93 -0
  158. package/dist/turnLatency.d.ts +95 -0
  159. package/dist/turnQuality.d.ts +94 -0
  160. package/dist/types.d.ts +170 -4
  161. package/dist/vue/VoiceDeliveryRuntime.d.ts +30 -0
  162. package/dist/vue/VoiceOpsActionCenter.d.ts +13 -0
  163. package/dist/vue/VoiceOpsStatus.d.ts +30 -0
  164. package/dist/vue/VoiceProviderCapabilities.d.ts +51 -0
  165. package/dist/vue/VoiceProviderContracts.d.ts +21 -0
  166. package/dist/vue/VoiceProviderSimulationControls.d.ts +88 -0
  167. package/dist/vue/VoiceProviderStatus.d.ts +51 -0
  168. package/dist/vue/VoiceRoutingStatus.d.ts +51 -0
  169. package/dist/vue/VoiceTurnLatency.d.ts +69 -0
  170. package/dist/vue/VoiceTurnQuality.d.ts +51 -0
  171. package/dist/vue/index.d.ts +22 -0
  172. package/dist/vue/index.js +3617 -53
  173. package/dist/vue/useVoiceCampaignDialerProof.d.ts +11 -0
  174. package/dist/vue/useVoiceController.d.ts +2 -1
  175. package/dist/vue/useVoiceDeliveryRuntime.d.ts +13 -0
  176. package/dist/vue/useVoiceOpsActionCenter.d.ts +11 -0
  177. package/dist/vue/useVoiceOpsStatus.d.ts +9 -0
  178. package/dist/vue/useVoiceProviderCapabilities.d.ts +9 -0
  179. package/dist/vue/useVoiceProviderContracts.d.ts +9 -0
  180. package/dist/vue/useVoiceProviderSimulationControls.d.ts +24 -0
  181. package/dist/vue/useVoiceProviderStatus.d.ts +1 -1
  182. package/dist/vue/useVoiceRoutingStatus.d.ts +8 -0
  183. package/dist/vue/useVoiceStream.d.ts +4 -1
  184. package/dist/vue/useVoiceTraceTimeline.d.ts +9 -0
  185. package/dist/vue/useVoiceTurnLatency.d.ts +10 -0
  186. package/dist/vue/useVoiceTurnQuality.d.ts +9 -0
  187. package/dist/vue/useVoiceWorkflowStatus.d.ts +9 -0
  188. package/dist/workflowContract.d.ts +91 -0
  189. package/package.json +1 -1
@@ -69,8 +69,3454 @@ var __decorateElement = (array, flags, name, decorators, target, extra) => {
69
69
  return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
70
70
  };
71
71
 
72
+ // src/react/useVoiceOpsStatus.tsx
73
+ import { useEffect, useRef, useSyncExternalStore } from "react";
74
+
75
+ // src/client/opsStatus.ts
76
+ var fetchVoiceOpsStatus = async (path = "/api/voice/ops-status", options = {}) => {
77
+ const fetchImpl = options.fetch ?? globalThis.fetch;
78
+ const response = await fetchImpl(path);
79
+ if (!response.ok) {
80
+ throw new Error(`Voice ops status failed: HTTP ${response.status}`);
81
+ }
82
+ return await response.json();
83
+ };
84
+ var createVoiceOpsStatusStore = (path = "/api/voice/ops-status", options = {}) => {
85
+ const listeners = new Set;
86
+ let closed = false;
87
+ let timer;
88
+ let snapshot = {
89
+ error: null,
90
+ isLoading: false
91
+ };
92
+ const emit = () => {
93
+ for (const listener of listeners) {
94
+ listener();
95
+ }
96
+ };
97
+ const refresh = async () => {
98
+ if (closed) {
99
+ return snapshot.report;
100
+ }
101
+ snapshot = {
102
+ ...snapshot,
103
+ error: null,
104
+ isLoading: true
105
+ };
106
+ emit();
107
+ try {
108
+ const report = await fetchVoiceOpsStatus(path, options);
109
+ snapshot = {
110
+ error: null,
111
+ isLoading: false,
112
+ report,
113
+ updatedAt: Date.now()
114
+ };
115
+ emit();
116
+ return report;
117
+ } catch (error) {
118
+ snapshot = {
119
+ ...snapshot,
120
+ error: error instanceof Error ? error.message : String(error),
121
+ isLoading: false
122
+ };
123
+ emit();
124
+ throw error;
125
+ }
126
+ };
127
+ const close = () => {
128
+ closed = true;
129
+ if (timer) {
130
+ clearInterval(timer);
131
+ timer = undefined;
132
+ }
133
+ listeners.clear();
134
+ };
135
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
136
+ timer = setInterval(() => {
137
+ refresh().catch(() => {});
138
+ }, options.intervalMs);
139
+ }
140
+ return {
141
+ close,
142
+ getServerSnapshot: () => snapshot,
143
+ getSnapshot: () => snapshot,
144
+ refresh,
145
+ subscribe: (listener) => {
146
+ listeners.add(listener);
147
+ return () => {
148
+ listeners.delete(listener);
149
+ };
150
+ }
151
+ };
152
+ };
153
+
154
+ // src/react/useVoiceOpsStatus.tsx
155
+ var useVoiceOpsStatus = (path = "/api/voice/ops-status", options = {}) => {
156
+ const storeRef = useRef(null);
157
+ if (!storeRef.current) {
158
+ storeRef.current = createVoiceOpsStatusStore(path, options);
159
+ }
160
+ const store = storeRef.current;
161
+ useEffect(() => {
162
+ store.refresh().catch(() => {});
163
+ return () => store.close();
164
+ }, [store]);
165
+ return {
166
+ ...useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot),
167
+ refresh: store.refresh
168
+ };
169
+ };
170
+
171
+ // src/client/opsStatusWidget.ts
172
+ var DEFAULT_TITLE = "Voice Ops Status";
173
+ var DEFAULT_DESCRIPTION = "Certified workflow, provider, and handoff readiness from your AbsoluteJS voice app.";
174
+ var SURFACE_LABELS = {
175
+ handoffs: "Handoffs",
176
+ providers: "Providers",
177
+ quality: "Quality",
178
+ sessions: "Sessions",
179
+ workflows: "Workflows"
180
+ };
181
+ var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
182
+ var readNumber = (value, key) => value && typeof value === "object" && (key in value) ? Number(value[key] ?? 0) : 0;
183
+ var surfaceDetail = (surface) => {
184
+ const total = readNumber(surface, "total");
185
+ const failed = readNumber(surface, "failed");
186
+ const degraded = readNumber(surface, "degraded");
187
+ const source = surface && typeof surface === "object" && "source" in surface && typeof surface.source === "string" ? ` from ${surface.source}` : "";
188
+ if (degraded > 0) {
189
+ return `${degraded} degraded of ${total}`;
190
+ }
191
+ if (failed > 0) {
192
+ return `${failed} failing of ${total}${source}`;
193
+ }
194
+ return total > 0 ? `${total} passing${source}` : `No failures${source}`;
195
+ };
196
+ var getVoiceOpsStatusLabel = (report, error) => {
197
+ if (error) {
198
+ return "Unavailable";
199
+ }
200
+ if (!report) {
201
+ return "Checking";
202
+ }
203
+ return report.status === "pass" ? "Passing" : "Needs attention";
204
+ };
205
+ var createVoiceOpsStatusViewModel = (snapshot, options = {}) => {
206
+ const report = snapshot.report;
207
+ const surfaces = Object.entries(report?.surfaces ?? {}).map(([id, surface]) => {
208
+ const failed = readNumber(surface, "failed") || readNumber(surface, "degraded");
209
+ const total = readNumber(surface, "total");
210
+ const status = surface && typeof surface === "object" && "status" in surface ? surface.status ?? "pass" : "pass";
211
+ return {
212
+ detail: surfaceDetail(surface),
213
+ failed,
214
+ id,
215
+ label: SURFACE_LABELS[id] ?? id,
216
+ status,
217
+ total
218
+ };
219
+ });
220
+ return {
221
+ description: options.description ?? DEFAULT_DESCRIPTION,
222
+ error: snapshot.error,
223
+ isLoading: snapshot.isLoading,
224
+ label: getVoiceOpsStatusLabel(report, snapshot.error),
225
+ links: options.includeLinks === false ? [] : report?.links ?? [],
226
+ passed: report?.passed ?? 0,
227
+ status: snapshot.error ? "error" : report ? report.status : snapshot.isLoading ? "loading" : "loading",
228
+ surfaces,
229
+ title: options.title ?? DEFAULT_TITLE,
230
+ total: report?.total ?? 0,
231
+ updatedAt: snapshot.updatedAt
232
+ };
233
+ };
234
+ var renderVoiceOpsStatusHTML = (snapshot, options = {}) => {
235
+ const model = createVoiceOpsStatusViewModel(snapshot, options);
236
+ const surfaces = model.surfaces.length ? model.surfaces.map((surface) => `<li class="absolute-voice-ops-status__surface absolute-voice-ops-status__surface--${escapeHtml(surface.status)}">
237
+ <span>${escapeHtml(surface.label)}</span>
238
+ <strong>${escapeHtml(surface.detail)}</strong>
239
+ </li>`).join("") : '<li class="absolute-voice-ops-status__surface"><span>Status</span><strong>Waiting for first check</strong></li>';
240
+ const links = model.links.length ? `<nav class="absolute-voice-ops-status__links">${model.links.slice(0, 4).map((link) => `<a href="${escapeHtml(link.href)}">${escapeHtml(link.label)}</a>`).join("")}</nav>` : "";
241
+ return `<section class="absolute-voice-ops-status absolute-voice-ops-status--${escapeHtml(model.status)}">
242
+ <header class="absolute-voice-ops-status__header">
243
+ <span class="absolute-voice-ops-status__eyebrow">${escapeHtml(model.title)}</span>
244
+ <strong class="absolute-voice-ops-status__label">${escapeHtml(model.label)}</strong>
245
+ </header>
246
+ <p class="absolute-voice-ops-status__description">${escapeHtml(model.description)}</p>
247
+ <div class="absolute-voice-ops-status__summary">
248
+ <span>${model.passed} passing</span>
249
+ <span>${Math.max(model.total - model.passed, 0)} failing</span>
250
+ <span>${model.total} checks</span>
251
+ </div>
252
+ <ul class="absolute-voice-ops-status__surfaces">${surfaces}</ul>
253
+ ${model.error ? `<p class="absolute-voice-ops-status__error">${escapeHtml(model.error)}</p>` : ""}
254
+ ${links}
255
+ </section>`;
256
+ };
257
+ var getVoiceOpsStatusCSS = () => `.absolute-voice-ops-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-ops-status--fail,.absolute-voice-ops-status--error{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-ops-status__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-ops-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-ops-status__label{font-size:28px;line-height:1}.absolute-voice-ops-status__description{color:#514733;margin:12px 0 0}.absolute-voice-ops-status__summary,.absolute-voice-ops-status__links{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.absolute-voice-ops-status__summary span,.absolute-voice-ops-status__links a{border:1px solid #e6ddca;border-radius:999px;color:inherit;padding:6px 10px;text-decoration:none}.absolute-voice-ops-status__surfaces{display:grid;gap:8px;list-style:none;margin:16px 0 0;padding:0}.absolute-voice-ops-status__surface{align-items:center;background:#fff;border:1px solid #eee4d2;border-radius:14px;display:flex;gap:12px;justify-content:space-between;padding:10px 12px}.absolute-voice-ops-status__surface--fail{border-color:#f2a7a7}.absolute-voice-ops-status__surface span{color:#655944}.absolute-voice-ops-status__error{color:#9f1239;font-weight:700}`;
258
+ var mountVoiceOpsStatus = (element, path = "/api/voice/ops-status", options = {}) => {
259
+ const store = createVoiceOpsStatusStore(path, options);
260
+ const render = () => {
261
+ element.innerHTML = renderVoiceOpsStatusHTML(store.getSnapshot(), options);
262
+ };
263
+ const unsubscribe = store.subscribe(render);
264
+ render();
265
+ store.refresh().catch(() => {});
266
+ return {
267
+ close: () => {
268
+ unsubscribe();
269
+ store.close();
270
+ },
271
+ refresh: store.refresh
272
+ };
273
+ };
274
+ var defineVoiceOpsStatusElement = (tagName = "absolute-voice-ops-status") => {
275
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
276
+ return;
277
+ }
278
+ customElements.define(tagName, class AbsoluteVoiceOpsStatusElement extends HTMLElement {
279
+ mounted;
280
+ connectedCallback() {
281
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
282
+ this.mounted = mountVoiceOpsStatus(this, this.getAttribute("path") ?? "/api/voice/ops-status", {
283
+ description: this.getAttribute("description") ?? undefined,
284
+ includeLinks: this.getAttribute("include-links") !== "false",
285
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
286
+ title: this.getAttribute("title") ?? undefined
287
+ });
288
+ }
289
+ disconnectedCallback() {
290
+ this.mounted?.close();
291
+ this.mounted = undefined;
292
+ }
293
+ });
294
+ };
295
+
296
+ // src/react/VoiceOpsStatus.tsx
297
+ import { jsxDEV } from "react/jsx-dev-runtime";
298
+ var VoiceOpsStatus = ({
299
+ className,
300
+ path = "/api/voice/ops-status",
301
+ ...options
302
+ }) => {
303
+ const snapshot = useVoiceOpsStatus(path, options);
304
+ const model = createVoiceOpsStatusViewModel(snapshot, options);
305
+ return /* @__PURE__ */ jsxDEV("section", {
306
+ className: [
307
+ "absolute-voice-ops-status",
308
+ `absolute-voice-ops-status--${model.status}`,
309
+ className
310
+ ].filter(Boolean).join(" "),
311
+ children: [
312
+ /* @__PURE__ */ jsxDEV("header", {
313
+ className: "absolute-voice-ops-status__header",
314
+ children: [
315
+ /* @__PURE__ */ jsxDEV("span", {
316
+ className: "absolute-voice-ops-status__eyebrow",
317
+ children: model.title
318
+ }, undefined, false, undefined, this),
319
+ /* @__PURE__ */ jsxDEV("strong", {
320
+ className: "absolute-voice-ops-status__label",
321
+ children: model.label
322
+ }, undefined, false, undefined, this)
323
+ ]
324
+ }, undefined, true, undefined, this),
325
+ /* @__PURE__ */ jsxDEV("p", {
326
+ className: "absolute-voice-ops-status__description",
327
+ children: model.description
328
+ }, undefined, false, undefined, this),
329
+ /* @__PURE__ */ jsxDEV("div", {
330
+ className: "absolute-voice-ops-status__summary",
331
+ children: [
332
+ /* @__PURE__ */ jsxDEV("span", {
333
+ children: [
334
+ model.passed,
335
+ " passing"
336
+ ]
337
+ }, undefined, true, undefined, this),
338
+ /* @__PURE__ */ jsxDEV("span", {
339
+ children: [
340
+ Math.max(model.total - model.passed, 0),
341
+ " failing"
342
+ ]
343
+ }, undefined, true, undefined, this),
344
+ /* @__PURE__ */ jsxDEV("span", {
345
+ children: [
346
+ model.total,
347
+ " checks"
348
+ ]
349
+ }, undefined, true, undefined, this)
350
+ ]
351
+ }, undefined, true, undefined, this),
352
+ /* @__PURE__ */ jsxDEV("ul", {
353
+ className: "absolute-voice-ops-status__surfaces",
354
+ children: model.surfaces.length > 0 ? model.surfaces.map((surface) => /* @__PURE__ */ jsxDEV("li", {
355
+ className: `absolute-voice-ops-status__surface absolute-voice-ops-status__surface--${surface.status}`,
356
+ children: [
357
+ /* @__PURE__ */ jsxDEV("span", {
358
+ children: surface.label
359
+ }, undefined, false, undefined, this),
360
+ /* @__PURE__ */ jsxDEV("strong", {
361
+ children: surface.detail
362
+ }, undefined, false, undefined, this)
363
+ ]
364
+ }, surface.id, true, undefined, this)) : /* @__PURE__ */ jsxDEV("li", {
365
+ className: "absolute-voice-ops-status__surface",
366
+ children: [
367
+ /* @__PURE__ */ jsxDEV("span", {
368
+ children: "Status"
369
+ }, undefined, false, undefined, this),
370
+ /* @__PURE__ */ jsxDEV("strong", {
371
+ children: "Waiting for first check"
372
+ }, undefined, false, undefined, this)
373
+ ]
374
+ }, undefined, true, undefined, this)
375
+ }, undefined, false, undefined, this),
376
+ model.error ? /* @__PURE__ */ jsxDEV("p", {
377
+ className: "absolute-voice-ops-status__error",
378
+ children: model.error
379
+ }, undefined, false, undefined, this) : null,
380
+ model.links.length > 0 ? /* @__PURE__ */ jsxDEV("nav", {
381
+ className: "absolute-voice-ops-status__links",
382
+ children: model.links.slice(0, 4).map((link) => /* @__PURE__ */ jsxDEV("a", {
383
+ href: link.href,
384
+ children: link.label
385
+ }, `${link.label}:${link.href}`, false, undefined, this))
386
+ }, undefined, false, undefined, this) : null
387
+ ]
388
+ }, undefined, true, undefined, this);
389
+ };
390
+ // src/client/opsActionCenter.ts
391
+ var recordVoiceOpsActionResult = async (result, options = {}) => {
392
+ if (options.auditPath === false) {
393
+ return;
394
+ }
395
+ const path = options.auditPath ?? "/api/voice/ops-actions/audit";
396
+ const fetchImpl = options.fetch ?? globalThis.fetch;
397
+ const response = await fetchImpl(path, {
398
+ body: JSON.stringify(result),
399
+ headers: {
400
+ "Content-Type": "application/json"
401
+ },
402
+ method: "POST"
403
+ });
404
+ if (!response.ok) {
405
+ throw new Error(`Voice ops action audit failed: HTTP ${response.status}`);
406
+ }
407
+ };
408
+ var createVoiceOpsActionCenterActions = (options = {}) => {
409
+ const deliveryRuntimePath = options.deliveryRuntimePath ?? "/api/voice-delivery-runtime";
410
+ const actions = [];
411
+ if (options.includeProductionReadiness !== false) {
412
+ actions.push({
413
+ description: "Refresh the production readiness report.",
414
+ id: "production-readiness",
415
+ label: "Refresh readiness",
416
+ method: "GET",
417
+ path: options.productionReadinessPath ?? "/api/production-readiness"
418
+ });
419
+ }
420
+ if (options.includeDeliveryRuntime !== false) {
421
+ actions.push({
422
+ description: "Drain pending and failed audit/trace deliveries.",
423
+ id: "delivery-runtime.tick",
424
+ label: "Tick delivery workers",
425
+ method: "POST",
426
+ path: `${deliveryRuntimePath.replace(/\/$/, "")}/tick`
427
+ }, {
428
+ description: "Move reviewed dead letters back to live delivery queues.",
429
+ id: "delivery-runtime.requeue-dead-letters",
430
+ label: "Requeue dead letters",
431
+ method: "POST",
432
+ path: `${deliveryRuntimePath.replace(/\/$/, "")}/requeue-dead-letters`
433
+ });
434
+ }
435
+ if (options.includeTurnLatencyProof !== false) {
436
+ actions.push({
437
+ description: "Run the synthetic turn latency proof.",
438
+ id: "turn-latency.proof",
439
+ label: "Run latency proof",
440
+ method: "POST",
441
+ path: options.turnLatencyProofPath ?? "/api/turn-latency/proof"
442
+ });
443
+ }
444
+ if (options.includeProviderSimulation !== false) {
445
+ const pathPrefix = options.providerSimulationPathPrefix ?? "/api/stt-simulate";
446
+ for (const provider of options.providers ?? []) {
447
+ actions.push({
448
+ description: `Simulate ${provider} provider failure.`,
449
+ id: `provider.${provider}.failure`,
450
+ label: `Simulate ${provider} failure`,
451
+ method: "POST",
452
+ path: `${pathPrefix}/failure?provider=${encodeURIComponent(provider)}`
453
+ }, {
454
+ description: `Mark ${provider} provider recovered.`,
455
+ id: `provider.${provider}.recovery`,
456
+ label: `Recover ${provider}`,
457
+ method: "POST",
458
+ path: `${pathPrefix}/recovery?provider=${encodeURIComponent(provider)}`
459
+ });
460
+ }
461
+ }
462
+ return actions;
463
+ };
464
+ var runVoiceOpsAction = async (action, options = {}) => {
465
+ const fetchImpl = options.fetch ?? globalThis.fetch;
466
+ const response = await fetchImpl(action.path, {
467
+ method: action.method ?? "POST"
468
+ });
469
+ const body = await response.json().catch(() => null);
470
+ if (!response.ok) {
471
+ const message = body && typeof body === "object" && "error" in body ? String(body.error) : `Voice ops action "${action.id}" failed: HTTP ${response.status}`;
472
+ throw new Error(message);
473
+ }
474
+ return {
475
+ actionId: action.id,
476
+ body,
477
+ ok: response.ok,
478
+ ranAt: Date.now(),
479
+ status: response.status
480
+ };
481
+ };
482
+ var createVoiceOpsActionCenterStore = (options = {}) => {
483
+ const listeners = new Set;
484
+ let closed = false;
485
+ let timer;
486
+ let snapshot = {
487
+ actions: options.actions ?? createVoiceOpsActionCenterActions(),
488
+ error: null,
489
+ isRunning: false
490
+ };
491
+ const emit = () => {
492
+ for (const listener of listeners) {
493
+ listener();
494
+ }
495
+ };
496
+ const setActions = (actions) => {
497
+ snapshot = { ...snapshot, actions, updatedAt: Date.now() };
498
+ emit();
499
+ };
500
+ const run = async (actionId) => {
501
+ if (closed) {
502
+ return snapshot.lastResult;
503
+ }
504
+ const action = snapshot.actions.find((item) => item.id === actionId);
505
+ if (!action) {
506
+ throw new Error(`Voice ops action "${actionId}" is not configured.`);
507
+ }
508
+ if (action.disabled) {
509
+ throw new Error(`Voice ops action "${actionId}" is disabled.`);
510
+ }
511
+ snapshot = {
512
+ ...snapshot,
513
+ error: null,
514
+ isRunning: true,
515
+ runningActionId: action.id
516
+ };
517
+ emit();
518
+ try {
519
+ const result = await runVoiceOpsAction(action, options);
520
+ await options.onActionResult?.(result);
521
+ await recordVoiceOpsActionResult(result, options);
522
+ snapshot = {
523
+ ...snapshot,
524
+ error: null,
525
+ isRunning: false,
526
+ lastResult: result,
527
+ runningActionId: undefined,
528
+ updatedAt: Date.now()
529
+ };
530
+ emit();
531
+ return result;
532
+ } catch (error) {
533
+ const result = {
534
+ actionId: action.id,
535
+ body: null,
536
+ error: error instanceof Error ? error.message : String(error),
537
+ ok: false,
538
+ ranAt: Date.now(),
539
+ status: 0
540
+ };
541
+ await options.onActionResult?.(result);
542
+ await recordVoiceOpsActionResult(result, options).catch(() => {});
543
+ snapshot = {
544
+ ...snapshot,
545
+ error: error instanceof Error ? error.message : String(error),
546
+ isRunning: false,
547
+ runningActionId: undefined
548
+ };
549
+ emit();
550
+ throw error;
551
+ }
552
+ };
553
+ const close = () => {
554
+ closed = true;
555
+ if (timer) {
556
+ clearInterval(timer);
557
+ timer = undefined;
558
+ }
559
+ listeners.clear();
560
+ };
561
+ if (options.intervalMs && options.intervalMs > 0) {
562
+ timer = setInterval(() => {
563
+ emit();
564
+ }, options.intervalMs);
565
+ }
566
+ return {
567
+ close,
568
+ getServerSnapshot: () => snapshot,
569
+ getSnapshot: () => snapshot,
570
+ run,
571
+ setActions,
572
+ subscribe: (listener) => {
573
+ listeners.add(listener);
574
+ return () => {
575
+ listeners.delete(listener);
576
+ };
577
+ }
578
+ };
579
+ };
580
+
581
+ // src/client/opsActionCenterWidget.ts
582
+ var DEFAULT_TITLE2 = "Voice Ops Action Center";
583
+ var DEFAULT_DESCRIPTION2 = "Run production voice proofs and operator actions from one primitive panel.";
584
+ var escapeHtml2 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
585
+ var createVoiceOpsActionCenterViewModel = (snapshot, options = {}) => {
586
+ const status = snapshot.error ? "error" : snapshot.isRunning ? "running" : snapshot.lastResult ? "completed" : "ready";
587
+ return {
588
+ actions: snapshot.actions.map((action) => ({
589
+ description: action.description ?? "",
590
+ disabled: Boolean(action.disabled || snapshot.isRunning),
591
+ id: action.id,
592
+ isRunning: snapshot.runningActionId === action.id,
593
+ label: action.label
594
+ })),
595
+ description: options.description ?? DEFAULT_DESCRIPTION2,
596
+ error: snapshot.error,
597
+ isRunning: snapshot.isRunning,
598
+ label: status === "error" ? "Needs attention" : status === "running" ? "Running" : status === "completed" ? "Action completed" : "Ready",
599
+ lastResultLabel: snapshot.lastResult ? `${snapshot.lastResult.actionId} returned HTTP ${snapshot.lastResult.status}` : "No action has run yet.",
600
+ status,
601
+ title: options.title ?? DEFAULT_TITLE2
602
+ };
603
+ };
604
+ var renderVoiceOpsActionCenterHTML = (snapshot, options = {}) => {
605
+ const model = createVoiceOpsActionCenterViewModel(snapshot, options);
606
+ const actions = model.actions.map((action) => `<button type="button" data-absolute-voice-ops-action="${escapeHtml2(action.id)}"${action.disabled ? " disabled" : ""}>
607
+ ${escapeHtml2(action.isRunning ? "Working..." : action.label)}
608
+ </button>`).join("");
609
+ return `<section class="absolute-voice-ops-action-center absolute-voice-ops-action-center--${escapeHtml2(model.status)}">
610
+ <header class="absolute-voice-ops-action-center__header">
611
+ <span class="absolute-voice-ops-action-center__eyebrow">${escapeHtml2(model.title)}</span>
612
+ <strong class="absolute-voice-ops-action-center__label">${escapeHtml2(model.label)}</strong>
613
+ </header>
614
+ <p class="absolute-voice-ops-action-center__description">${escapeHtml2(model.description)}</p>
615
+ <div class="absolute-voice-ops-action-center__actions">${actions}</div>
616
+ <p class="absolute-voice-ops-action-center__result">${escapeHtml2(model.lastResultLabel)}</p>
617
+ ${model.error ? `<p class="absolute-voice-ops-action-center__error">${escapeHtml2(model.error)}</p>` : ""}
618
+ </section>`;
619
+ };
620
+ 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}`;
621
+ var mountVoiceOpsActionCenter = (element, options = {}) => {
622
+ const store = createVoiceOpsActionCenterStore(options);
623
+ const render = () => {
624
+ element.innerHTML = renderVoiceOpsActionCenterHTML(store.getSnapshot(), options);
625
+ };
626
+ const unsubscribe = store.subscribe(render);
627
+ const handleClick = (event) => {
628
+ const target = event.target;
629
+ if (!(target instanceof Element)) {
630
+ return;
631
+ }
632
+ const action = target.closest("[data-absolute-voice-ops-action]");
633
+ const actionId = action?.getAttribute("data-absolute-voice-ops-action");
634
+ if (actionId) {
635
+ store.run(actionId).catch(() => {});
636
+ }
637
+ };
638
+ element.addEventListener?.("click", handleClick);
639
+ render();
640
+ return {
641
+ close: () => {
642
+ element.removeEventListener?.("click", handleClick);
643
+ unsubscribe();
644
+ store.close();
645
+ },
646
+ run: store.run
647
+ };
648
+ };
649
+ var defineVoiceOpsActionCenterElement = (tagName = "absolute-voice-ops-action-center", options = {}) => {
650
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
651
+ return;
652
+ }
653
+ customElements.define(tagName, class AbsoluteVoiceOpsActionCenterElement extends HTMLElement {
654
+ mounted;
655
+ connectedCallback() {
656
+ this.mounted = mountVoiceOpsActionCenter(this, {
657
+ ...options,
658
+ description: this.getAttribute("description") ?? options.description,
659
+ title: this.getAttribute("title") ?? options.title
660
+ });
661
+ }
662
+ disconnectedCallback() {
663
+ this.mounted?.close();
664
+ this.mounted = undefined;
665
+ }
666
+ });
667
+ };
668
+
669
+ // src/react/useVoiceOpsActionCenter.tsx
670
+ import { useEffect as useEffect2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2 } from "react";
671
+ var useVoiceOpsActionCenter = (options = {}) => {
672
+ const storeRef = useRef2(null);
673
+ if (!storeRef.current) {
674
+ storeRef.current = createVoiceOpsActionCenterStore(options);
675
+ }
676
+ const store = storeRef.current;
677
+ useEffect2(() => () => store.close(), [store]);
678
+ return {
679
+ ...useSyncExternalStore2(store.subscribe, store.getSnapshot, store.getServerSnapshot),
680
+ run: store.run,
681
+ setActions: store.setActions
682
+ };
683
+ };
684
+
685
+ // src/react/VoiceOpsActionCenter.tsx
686
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
687
+ var VoiceOpsActionCenter = ({
688
+ className,
689
+ ...options
690
+ }) => {
691
+ const snapshot = useVoiceOpsActionCenter(options);
692
+ const model = createVoiceOpsActionCenterViewModel(snapshot, options);
693
+ return /* @__PURE__ */ jsxDEV2("section", {
694
+ className: [
695
+ "absolute-voice-ops-action-center",
696
+ `absolute-voice-ops-action-center--${model.status}`,
697
+ className
698
+ ].filter(Boolean).join(" "),
699
+ children: [
700
+ /* @__PURE__ */ jsxDEV2("header", {
701
+ className: "absolute-voice-ops-action-center__header",
702
+ children: [
703
+ /* @__PURE__ */ jsxDEV2("span", {
704
+ className: "absolute-voice-ops-action-center__eyebrow",
705
+ children: model.title
706
+ }, undefined, false, undefined, this),
707
+ /* @__PURE__ */ jsxDEV2("strong", {
708
+ className: "absolute-voice-ops-action-center__label",
709
+ children: model.label
710
+ }, undefined, false, undefined, this)
711
+ ]
712
+ }, undefined, true, undefined, this),
713
+ /* @__PURE__ */ jsxDEV2("p", {
714
+ className: "absolute-voice-ops-action-center__description",
715
+ children: model.description
716
+ }, undefined, false, undefined, this),
717
+ /* @__PURE__ */ jsxDEV2("div", {
718
+ className: "absolute-voice-ops-action-center__actions",
719
+ children: model.actions.map((action) => /* @__PURE__ */ jsxDEV2("button", {
720
+ disabled: action.disabled,
721
+ onClick: () => {
722
+ snapshot.run(action.id).catch(() => {});
723
+ },
724
+ type: "button",
725
+ children: action.isRunning ? "Working..." : action.label
726
+ }, action.id, false, undefined, this))
727
+ }, undefined, false, undefined, this),
728
+ /* @__PURE__ */ jsxDEV2("p", {
729
+ className: "absolute-voice-ops-action-center__result",
730
+ children: model.lastResultLabel
731
+ }, undefined, false, undefined, this),
732
+ model.error ? /* @__PURE__ */ jsxDEV2("p", {
733
+ className: "absolute-voice-ops-action-center__error",
734
+ children: model.error
735
+ }, undefined, false, undefined, this) : null
736
+ ]
737
+ }, undefined, true, undefined, this);
738
+ };
739
+ // src/client/deliveryRuntime.ts
740
+ var getDefaultActionPath = (path, action, options) => {
741
+ if (action === "tick") {
742
+ return options.tickPath ?? `${path.replace(/\/$/, "")}/tick`;
743
+ }
744
+ return options.requeueDeadLettersPath ?? `${path.replace(/\/$/, "")}/requeue-dead-letters`;
745
+ };
746
+ var fetchVoiceDeliveryRuntime = async (path = "/api/voice-delivery-runtime", options = {}) => {
747
+ const fetchImpl = options.fetch ?? globalThis.fetch;
748
+ const response = await fetchImpl(path);
749
+ if (!response.ok) {
750
+ throw new Error(`Voice delivery runtime failed: HTTP ${response.status}`);
751
+ }
752
+ return await response.json();
753
+ };
754
+ var runVoiceDeliveryRuntimeAction = async (action, path = "/api/voice-delivery-runtime", options = {}) => {
755
+ const fetchImpl = options.fetch ?? globalThis.fetch;
756
+ const response = await fetchImpl(getDefaultActionPath(path, action, options), {
757
+ method: "POST"
758
+ });
759
+ if (!response.ok) {
760
+ throw new Error(`Voice delivery runtime ${action} failed: HTTP ${response.status}`);
761
+ }
762
+ const body = await response.json();
763
+ return {
764
+ action,
765
+ result: body.result,
766
+ summary: body.summary,
767
+ updatedAt: Date.now()
768
+ };
769
+ };
770
+ var createVoiceDeliveryRuntimeStore = (path = "/api/voice-delivery-runtime", options = {}) => {
771
+ const listeners = new Set;
772
+ let closed = false;
773
+ let timer;
774
+ let snapshot = {
775
+ actionError: null,
776
+ actionStatus: "idle",
777
+ error: null,
778
+ isLoading: false
779
+ };
780
+ const emit = () => {
781
+ for (const listener of listeners) {
782
+ listener();
783
+ }
784
+ };
785
+ const refresh = async () => {
786
+ if (closed) {
787
+ return snapshot.report;
788
+ }
789
+ snapshot = {
790
+ ...snapshot,
791
+ error: null,
792
+ isLoading: true
793
+ };
794
+ emit();
795
+ try {
796
+ const report = await fetchVoiceDeliveryRuntime(path, options);
797
+ snapshot = {
798
+ ...snapshot,
799
+ error: null,
800
+ isLoading: false,
801
+ report,
802
+ updatedAt: Date.now()
803
+ };
804
+ emit();
805
+ return report;
806
+ } catch (error) {
807
+ snapshot = {
808
+ ...snapshot,
809
+ error: error instanceof Error ? error.message : String(error),
810
+ isLoading: false
811
+ };
812
+ emit();
813
+ throw error;
814
+ }
815
+ };
816
+ const runAction = async (action) => {
817
+ if (closed) {
818
+ return snapshot.lastAction;
819
+ }
820
+ snapshot = {
821
+ ...snapshot,
822
+ actionError: null,
823
+ actionStatus: "running"
824
+ };
825
+ emit();
826
+ try {
827
+ const result = await runVoiceDeliveryRuntimeAction(action, path, options);
828
+ snapshot = {
829
+ ...snapshot,
830
+ actionError: null,
831
+ actionStatus: "completed",
832
+ lastAction: result
833
+ };
834
+ emit();
835
+ await refresh();
836
+ return result;
837
+ } catch (error) {
838
+ snapshot = {
839
+ ...snapshot,
840
+ actionError: error instanceof Error ? error.message : String(error),
841
+ actionStatus: "failed"
842
+ };
843
+ emit();
844
+ throw error;
845
+ }
846
+ };
847
+ const close = () => {
848
+ closed = true;
849
+ if (timer) {
850
+ clearInterval(timer);
851
+ timer = undefined;
852
+ }
853
+ listeners.clear();
854
+ };
855
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
856
+ timer = setInterval(() => {
857
+ refresh().catch(() => {});
858
+ }, options.intervalMs);
859
+ }
860
+ return {
861
+ close,
862
+ getServerSnapshot: () => snapshot,
863
+ getSnapshot: () => snapshot,
864
+ requeueDeadLetters: () => runAction("requeue-dead-letters"),
865
+ refresh,
866
+ tick: () => runAction("tick"),
867
+ subscribe: (listener) => {
868
+ listeners.add(listener);
869
+ return () => {
870
+ listeners.delete(listener);
871
+ };
872
+ }
873
+ };
874
+ };
875
+
876
+ // src/client/deliveryRuntimeWidget.ts
877
+ var DEFAULT_TITLE3 = "Voice Delivery Runtime";
878
+ var DEFAULT_DESCRIPTION3 = "Audit and trace delivery worker health from your AbsoluteJS voice app.";
879
+ var escapeHtml3 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
880
+ var createSurface = (id, summary) => {
881
+ if (!summary) {
882
+ return {
883
+ deadLettered: 0,
884
+ detail: "Worker disabled",
885
+ failed: 0,
886
+ id,
887
+ label: id === "audit" ? "Audit delivery" : "Trace delivery",
888
+ pending: 0,
889
+ status: "disabled",
890
+ total: 0
891
+ };
892
+ }
893
+ const blocked = summary.failed + summary.deadLettered;
894
+ return {
895
+ deadLettered: summary.deadLettered,
896
+ detail: `${summary.delivered}/${summary.total} delivered, ${summary.pending} pending`,
897
+ failed: summary.failed,
898
+ id,
899
+ label: id === "audit" ? "Audit delivery" : "Trace delivery",
900
+ pending: summary.pending,
901
+ status: blocked > 0 ? "warn" : "pass",
902
+ total: summary.total
903
+ };
904
+ };
905
+ var createVoiceDeliveryRuntimeViewModel = (snapshot, options = {}) => {
906
+ const report = snapshot.report;
907
+ const surfaces = [
908
+ createSurface("audit", report?.summary.audit),
909
+ createSurface("trace", report?.summary.trace)
910
+ ];
911
+ const hasWarnings = surfaces.some((surface) => surface.status === "warn");
912
+ return {
913
+ description: options.description ?? DEFAULT_DESCRIPTION3,
914
+ error: snapshot.error,
915
+ actionError: snapshot.actionError,
916
+ actionStatus: snapshot.actionStatus,
917
+ isLoading: snapshot.isLoading,
918
+ isRunning: Boolean(report?.isRunning),
919
+ label: snapshot.error ? "Unavailable" : report ? report.isRunning ? "Running" : "Stopped" : "Checking",
920
+ status: snapshot.error ? "error" : report ? hasWarnings ? "warn" : "pass" : "loading",
921
+ surfaces,
922
+ title: options.title ?? DEFAULT_TITLE3,
923
+ updatedAt: snapshot.updatedAt
924
+ };
925
+ };
926
+ var renderVoiceDeliveryRuntimeHTML = (snapshot, options = {}) => {
927
+ const model = createVoiceDeliveryRuntimeViewModel(snapshot, options);
928
+ const surfaces = model.surfaces.map((surface) => `<li class="absolute-voice-delivery-runtime__surface absolute-voice-delivery-runtime__surface--${escapeHtml3(surface.status)}">
929
+ <span>${escapeHtml3(surface.label)}</span>
930
+ <strong>${escapeHtml3(surface.detail)}</strong>
931
+ <small>${String(surface.failed)} failed &middot; ${String(surface.deadLettered)} dead-lettered</small>
932
+ </li>`).join("");
933
+ const actions = options.includeActions === false ? "" : `<div class="absolute-voice-delivery-runtime__actions">
934
+ <button type="button" data-absolute-voice-delivery-runtime-action="tick">${model.actionStatus === "running" ? "Working..." : "Tick workers"}</button>
935
+ <button type="button" data-absolute-voice-delivery-runtime-action="requeue-dead-letters"${model.surfaces.some((surface) => surface.deadLettered > 0) ? "" : " disabled"}>Requeue dead letters</button>
936
+ </div>`;
937
+ const actionError = model.actionError ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml3(model.actionError)}</p>` : "";
938
+ return `<section class="absolute-voice-delivery-runtime absolute-voice-delivery-runtime--${escapeHtml3(model.status)}">
939
+ <header class="absolute-voice-delivery-runtime__header">
940
+ <span class="absolute-voice-delivery-runtime__eyebrow">${escapeHtml3(model.title)}</span>
941
+ <strong class="absolute-voice-delivery-runtime__label">${escapeHtml3(model.label)}</strong>
942
+ </header>
943
+ <p class="absolute-voice-delivery-runtime__description">${escapeHtml3(model.description)}</p>
944
+ <ul class="absolute-voice-delivery-runtime__surfaces">${surfaces}</ul>
945
+ ${actions}
946
+ ${actionError}
947
+ ${model.error ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml3(model.error)}</p>` : ""}
948
+ </section>`;
949
+ };
950
+ 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}`;
951
+ var mountVoiceDeliveryRuntime = (element, path = "/api/voice-delivery-runtime", options = {}) => {
952
+ const store = createVoiceDeliveryRuntimeStore(path, options);
953
+ const render = () => {
954
+ element.innerHTML = renderVoiceDeliveryRuntimeHTML(store.getSnapshot(), options);
955
+ };
956
+ const unsubscribe = store.subscribe(render);
957
+ const handleClick = (event) => {
958
+ const target = event.target;
959
+ if (!(target instanceof Element)) {
960
+ return;
961
+ }
962
+ const action = target.closest("[data-absolute-voice-delivery-runtime-action]");
963
+ const actionName = action?.getAttribute("data-absolute-voice-delivery-runtime-action");
964
+ if (actionName === "tick") {
965
+ store.tick().catch(() => {});
966
+ }
967
+ if (actionName === "requeue-dead-letters") {
968
+ store.requeueDeadLetters().catch(() => {});
969
+ }
970
+ };
971
+ element.addEventListener?.("click", handleClick);
972
+ render();
973
+ store.refresh().catch(() => {});
974
+ return {
975
+ close: () => {
976
+ element.removeEventListener?.("click", handleClick);
977
+ unsubscribe();
978
+ store.close();
979
+ },
980
+ refresh: store.refresh
981
+ };
982
+ };
983
+ var defineVoiceDeliveryRuntimeElement = (tagName = "absolute-voice-delivery-runtime") => {
984
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
985
+ return;
986
+ }
987
+ customElements.define(tagName, class AbsoluteVoiceDeliveryRuntimeElement extends HTMLElement {
988
+ mounted;
989
+ connectedCallback() {
990
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
991
+ this.mounted = mountVoiceDeliveryRuntime(this, this.getAttribute("path") ?? "/api/voice-delivery-runtime", {
992
+ description: this.getAttribute("description") ?? undefined,
993
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
994
+ title: this.getAttribute("title") ?? undefined
995
+ });
996
+ }
997
+ disconnectedCallback() {
998
+ this.mounted?.close();
999
+ this.mounted = undefined;
1000
+ }
1001
+ });
1002
+ };
1003
+
1004
+ // src/react/useVoiceDeliveryRuntime.tsx
1005
+ import { useEffect as useEffect3, useRef as useRef3, useSyncExternalStore as useSyncExternalStore3 } from "react";
1006
+ var useVoiceDeliveryRuntime = (path = "/api/voice-delivery-runtime", options = {}) => {
1007
+ const storeRef = useRef3(null);
1008
+ if (!storeRef.current) {
1009
+ storeRef.current = createVoiceDeliveryRuntimeStore(path, options);
1010
+ }
1011
+ const store = storeRef.current;
1012
+ useEffect3(() => {
1013
+ store.refresh().catch(() => {});
1014
+ return () => store.close();
1015
+ }, [store]);
1016
+ return {
1017
+ ...useSyncExternalStore3(store.subscribe, store.getSnapshot, store.getServerSnapshot),
1018
+ requeueDeadLetters: store.requeueDeadLetters,
1019
+ refresh: store.refresh,
1020
+ tick: store.tick
1021
+ };
1022
+ };
1023
+
1024
+ // src/react/VoiceDeliveryRuntime.tsx
1025
+ import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
1026
+ var VoiceDeliveryRuntime = ({
1027
+ className,
1028
+ includeActions = true,
1029
+ path = "/api/voice-delivery-runtime",
1030
+ ...options
1031
+ }) => {
1032
+ const snapshot = useVoiceDeliveryRuntime(path, options);
1033
+ const model = createVoiceDeliveryRuntimeViewModel(snapshot, options);
1034
+ const hasDeadLetters = model.surfaces.some((surface) => surface.deadLettered > 0);
1035
+ return /* @__PURE__ */ jsxDEV3("section", {
1036
+ className: [
1037
+ "absolute-voice-delivery-runtime",
1038
+ `absolute-voice-delivery-runtime--${model.status}`,
1039
+ className
1040
+ ].filter(Boolean).join(" "),
1041
+ children: [
1042
+ /* @__PURE__ */ jsxDEV3("header", {
1043
+ className: "absolute-voice-delivery-runtime__header",
1044
+ children: [
1045
+ /* @__PURE__ */ jsxDEV3("span", {
1046
+ className: "absolute-voice-delivery-runtime__eyebrow",
1047
+ children: model.title
1048
+ }, undefined, false, undefined, this),
1049
+ /* @__PURE__ */ jsxDEV3("strong", {
1050
+ className: "absolute-voice-delivery-runtime__label",
1051
+ children: model.label
1052
+ }, undefined, false, undefined, this)
1053
+ ]
1054
+ }, undefined, true, undefined, this),
1055
+ /* @__PURE__ */ jsxDEV3("p", {
1056
+ className: "absolute-voice-delivery-runtime__description",
1057
+ children: model.description
1058
+ }, undefined, false, undefined, this),
1059
+ /* @__PURE__ */ jsxDEV3("ul", {
1060
+ className: "absolute-voice-delivery-runtime__surfaces",
1061
+ children: model.surfaces.map((surface) => /* @__PURE__ */ jsxDEV3("li", {
1062
+ className: `absolute-voice-delivery-runtime__surface absolute-voice-delivery-runtime__surface--${surface.status}`,
1063
+ children: [
1064
+ /* @__PURE__ */ jsxDEV3("span", {
1065
+ children: surface.label
1066
+ }, undefined, false, undefined, this),
1067
+ /* @__PURE__ */ jsxDEV3("strong", {
1068
+ children: surface.detail
1069
+ }, undefined, false, undefined, this),
1070
+ /* @__PURE__ */ jsxDEV3("small", {
1071
+ children: [
1072
+ surface.failed,
1073
+ " failed / ",
1074
+ surface.deadLettered,
1075
+ " dead-lettered"
1076
+ ]
1077
+ }, undefined, true, undefined, this)
1078
+ ]
1079
+ }, surface.id, true, undefined, this))
1080
+ }, undefined, false, undefined, this),
1081
+ includeActions ? /* @__PURE__ */ jsxDEV3("div", {
1082
+ className: "absolute-voice-delivery-runtime__actions",
1083
+ children: [
1084
+ /* @__PURE__ */ jsxDEV3("button", {
1085
+ disabled: model.actionStatus === "running",
1086
+ onClick: () => {
1087
+ snapshot.tick().catch(() => {});
1088
+ },
1089
+ type: "button",
1090
+ children: model.actionStatus === "running" ? "Working..." : "Tick workers"
1091
+ }, undefined, false, undefined, this),
1092
+ /* @__PURE__ */ jsxDEV3("button", {
1093
+ disabled: model.actionStatus === "running" || !hasDeadLetters,
1094
+ onClick: () => {
1095
+ snapshot.requeueDeadLetters().catch(() => {});
1096
+ },
1097
+ type: "button",
1098
+ children: "Requeue dead letters"
1099
+ }, undefined, false, undefined, this)
1100
+ ]
1101
+ }, undefined, true, undefined, this) : null,
1102
+ model.actionError ? /* @__PURE__ */ jsxDEV3("p", {
1103
+ className: "absolute-voice-delivery-runtime__error",
1104
+ children: model.actionError
1105
+ }, undefined, false, undefined, this) : null,
1106
+ model.error ? /* @__PURE__ */ jsxDEV3("p", {
1107
+ className: "absolute-voice-delivery-runtime__error",
1108
+ children: model.error
1109
+ }, undefined, false, undefined, this) : null
1110
+ ]
1111
+ }, undefined, true, undefined, this);
1112
+ };
1113
+ // src/client/providerSimulationControls.ts
1114
+ var postSimulation = async (pathPrefix, mode, provider, fetchImpl) => {
1115
+ const response = await fetchImpl(`${pathPrefix}/${mode}?provider=${encodeURIComponent(provider)}`, { method: "POST" });
1116
+ const body = await response.json().catch(() => null);
1117
+ if (!response.ok) {
1118
+ const message = body && typeof body === "object" && "error" in body ? String(body.error) : `Voice provider simulation failed: HTTP ${response.status}`;
1119
+ throw new Error(message);
1120
+ }
1121
+ return body;
1122
+ };
1123
+ var createVoiceProviderSimulationControlsStore = (options) => {
1124
+ const listeners = new Set;
1125
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1126
+ const pathPrefix = options.pathPrefix ?? `/api/${options.kind ?? "stt"}-simulate`;
1127
+ let closed = false;
1128
+ let snapshot = {
1129
+ error: null,
1130
+ isRunning: false,
1131
+ lastResult: null,
1132
+ mode: null,
1133
+ provider: null
1134
+ };
1135
+ const emit = () => {
1136
+ for (const listener of listeners) {
1137
+ listener();
1138
+ }
1139
+ };
1140
+ const run = async (provider, mode) => {
1141
+ if (closed) {
1142
+ return snapshot.lastResult;
1143
+ }
1144
+ snapshot = {
1145
+ ...snapshot,
1146
+ error: null,
1147
+ isRunning: true,
1148
+ mode,
1149
+ provider
1150
+ };
1151
+ emit();
1152
+ try {
1153
+ const result = await postSimulation(pathPrefix, mode, provider, fetchImpl);
1154
+ snapshot = {
1155
+ error: null,
1156
+ isRunning: false,
1157
+ lastResult: result,
1158
+ mode,
1159
+ provider,
1160
+ updatedAt: Date.now()
1161
+ };
1162
+ emit();
1163
+ return result;
1164
+ } catch (error) {
1165
+ snapshot = {
1166
+ ...snapshot,
1167
+ error: error instanceof Error ? error.message : String(error),
1168
+ isRunning: false
1169
+ };
1170
+ emit();
1171
+ throw error;
1172
+ }
1173
+ };
1174
+ const close = () => {
1175
+ closed = true;
1176
+ listeners.clear();
1177
+ };
1178
+ return {
1179
+ close,
1180
+ getServerSnapshot: () => snapshot,
1181
+ getSnapshot: () => snapshot,
1182
+ run,
1183
+ subscribe: (listener) => {
1184
+ listeners.add(listener);
1185
+ return () => {
1186
+ listeners.delete(listener);
1187
+ };
1188
+ }
1189
+ };
1190
+ };
1191
+
1192
+ // src/client/providerSimulationControlsWidget.ts
1193
+ var escapeHtml4 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1194
+ var formatKind = (kind) => (kind ?? "stt").toUpperCase();
1195
+ var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
1196
+ const configuredProviders = options.providers.filter((provider) => provider.configured !== false);
1197
+ const fallbackReady = !options.fallbackRequiredProvider || configuredProviders.some((entry) => entry.provider === options.fallbackRequiredProvider);
1198
+ const failureProviders = (options.failureProviders ? options.failureProviders.map((provider) => ({ provider })) : configuredProviders).filter((provider) => configuredProviders.some((entry) => entry.provider === provider.provider));
1199
+ return {
1200
+ canSimulateFailure: configuredProviders.length > 0 && fallbackReady,
1201
+ description: options.failureMessage ?? `Simulate ${formatKind(options.kind)} provider failure and recovery without changing credentials.`,
1202
+ error: snapshot.error,
1203
+ failureProviders,
1204
+ isRunning: snapshot.isRunning,
1205
+ label: snapshot.isRunning ? `Running ${snapshot.mode ?? "simulation"}` : snapshot.lastResult ? `${snapshot.lastResult.provider} ${snapshot.lastResult.mode} simulated` : configuredProviders.length ? `${configuredProviders.length} configured` : "No configured providers",
1206
+ providers: configuredProviders,
1207
+ resultText: snapshot.lastResult ? JSON.stringify(snapshot.lastResult, null, 2) : null,
1208
+ title: options.title ?? `${formatKind(options.kind)} Failure Simulation`
1209
+ };
1210
+ };
1211
+ var renderVoiceProviderSimulationControlsHTML = (snapshot, options) => {
1212
+ const model = createVoiceProviderSimulationControlsViewModel(snapshot, options);
1213
+ 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("");
1214
+ 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("");
1215
+ return `<section class="absolute-voice-provider-simulation absolute-voice-provider-simulation--${snapshot.error ? "error" : snapshot.isRunning ? "running" : "ready"}">
1216
+ <header class="absolute-voice-provider-simulation__header">
1217
+ <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml4(model.title)}</span>
1218
+ <strong class="absolute-voice-provider-simulation__label">${escapeHtml4(model.label)}</strong>
1219
+ </header>
1220
+ <p class="absolute-voice-provider-simulation__description">${escapeHtml4(model.description)}</p>
1221
+ ${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml4(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
1222
+ <div class="absolute-voice-provider-simulation__actions">${failureButtons}${recoveryButtons}</div>
1223
+ ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml4(snapshot.error)}</p>` : ""}
1224
+ ${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml4(model.resultText)}</pre>` : ""}
1225
+ </section>`;
1226
+ };
1227
+ var bindVoiceProviderSimulationControls = (element, store) => {
1228
+ const onClick = (event) => {
1229
+ const target = event.target;
1230
+ if (!(target instanceof HTMLElement)) {
1231
+ return;
1232
+ }
1233
+ const failProvider = target.getAttribute("data-voice-provider-fail");
1234
+ const recoverProvider = target.getAttribute("data-voice-provider-recover");
1235
+ if (failProvider) {
1236
+ store.run(failProvider, "failure").catch(() => {});
1237
+ }
1238
+ if (recoverProvider) {
1239
+ store.run(recoverProvider, "recovery").catch(() => {});
1240
+ }
1241
+ };
1242
+ element.addEventListener("click", onClick);
1243
+ return () => element.removeEventListener("click", onClick);
1244
+ };
1245
+ var mountVoiceProviderSimulationControls = (element, options) => {
1246
+ const store = createVoiceProviderSimulationControlsStore(options);
1247
+ const render = () => {
1248
+ element.innerHTML = renderVoiceProviderSimulationControlsHTML(store.getSnapshot(), options);
1249
+ };
1250
+ const unsubscribeStore = store.subscribe(render);
1251
+ const unsubscribeDom = bindVoiceProviderSimulationControls(element, store);
1252
+ render();
1253
+ return {
1254
+ close: () => {
1255
+ unsubscribeDom();
1256
+ unsubscribeStore();
1257
+ store.close();
1258
+ },
1259
+ run: store.run
1260
+ };
1261
+ };
1262
+ var defineVoiceProviderSimulationControlsElement = (tagName = "absolute-voice-provider-simulation") => {
1263
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
1264
+ return;
1265
+ }
1266
+ customElements.define(tagName, class AbsoluteVoiceProviderSimulationElement extends HTMLElement {
1267
+ mounted;
1268
+ connectedCallback() {
1269
+ const providers = (this.getAttribute("providers") ?? "").split(",").map((provider) => provider.trim()).filter(Boolean).map((provider) => ({ provider }));
1270
+ const failureProviders = (this.getAttribute("failure-providers") ?? "").split(",").map((provider) => provider.trim()).filter(Boolean);
1271
+ this.mounted = mountVoiceProviderSimulationControls(this, {
1272
+ failureProviders: failureProviders.length ? failureProviders : undefined,
1273
+ fallbackRequiredMessage: this.getAttribute("fallback-required-message") ?? undefined,
1274
+ fallbackRequiredProvider: this.getAttribute("fallback-required-provider") ?? undefined,
1275
+ failureMessage: this.getAttribute("failure-message") ?? undefined,
1276
+ kind: this.getAttribute("kind") ?? "stt",
1277
+ pathPrefix: this.getAttribute("path-prefix") ?? undefined,
1278
+ providers,
1279
+ title: this.getAttribute("title") ?? undefined
1280
+ });
1281
+ }
1282
+ disconnectedCallback() {
1283
+ this.mounted?.close();
1284
+ this.mounted = undefined;
1285
+ }
1286
+ });
1287
+ };
1288
+
1289
+ // src/react/useVoiceProviderSimulationControls.tsx
1290
+ import { useEffect as useEffect4, useRef as useRef4, useSyncExternalStore as useSyncExternalStore4 } from "react";
1291
+ var useVoiceProviderSimulationControls = (options) => {
1292
+ const storeRef = useRef4(null);
1293
+ if (!storeRef.current) {
1294
+ storeRef.current = createVoiceProviderSimulationControlsStore(options);
1295
+ }
1296
+ const store = storeRef.current;
1297
+ useEffect4(() => () => store.close(), [store]);
1298
+ return {
1299
+ ...useSyncExternalStore4(store.subscribe, store.getSnapshot, store.getServerSnapshot),
1300
+ run: store.run
1301
+ };
1302
+ };
1303
+
1304
+ // src/react/VoiceProviderSimulationControls.tsx
1305
+ import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
1306
+ var VoiceProviderSimulationControls = ({
1307
+ className,
1308
+ ...options
1309
+ }) => {
1310
+ const snapshot = useVoiceProviderSimulationControls(options);
1311
+ const model = createVoiceProviderSimulationControlsViewModel(snapshot, options);
1312
+ const run = (provider, mode) => {
1313
+ snapshot.run(provider, mode).catch(() => {});
1314
+ };
1315
+ return /* @__PURE__ */ jsxDEV4("section", {
1316
+ className: [
1317
+ "absolute-voice-provider-simulation",
1318
+ `absolute-voice-provider-simulation--${snapshot.error ? "error" : snapshot.isRunning ? "running" : "ready"}`,
1319
+ className
1320
+ ].filter(Boolean).join(" "),
1321
+ children: [
1322
+ /* @__PURE__ */ jsxDEV4("header", {
1323
+ className: "absolute-voice-provider-simulation__header",
1324
+ children: [
1325
+ /* @__PURE__ */ jsxDEV4("span", {
1326
+ className: "absolute-voice-provider-simulation__eyebrow",
1327
+ children: model.title
1328
+ }, undefined, false, undefined, this),
1329
+ /* @__PURE__ */ jsxDEV4("strong", {
1330
+ className: "absolute-voice-provider-simulation__label",
1331
+ children: model.label
1332
+ }, undefined, false, undefined, this)
1333
+ ]
1334
+ }, undefined, true, undefined, this),
1335
+ /* @__PURE__ */ jsxDEV4("p", {
1336
+ className: "absolute-voice-provider-simulation__description",
1337
+ children: model.description
1338
+ }, undefined, false, undefined, this),
1339
+ model.canSimulateFailure ? null : /* @__PURE__ */ jsxDEV4("p", {
1340
+ className: "absolute-voice-provider-simulation__empty",
1341
+ children: options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure."
1342
+ }, undefined, false, undefined, this),
1343
+ /* @__PURE__ */ jsxDEV4("div", {
1344
+ className: "absolute-voice-provider-simulation__actions",
1345
+ children: [
1346
+ model.failureProviders.map((provider) => /* @__PURE__ */ jsxDEV4("button", {
1347
+ disabled: !model.canSimulateFailure || snapshot.isRunning,
1348
+ onClick: () => run(provider.provider, "failure"),
1349
+ type: "button",
1350
+ children: [
1351
+ "Simulate ",
1352
+ provider.provider,
1353
+ " ",
1354
+ (options.kind ?? "stt").toUpperCase(),
1355
+ " ",
1356
+ "failure"
1357
+ ]
1358
+ }, `fail-${provider.provider}`, true, undefined, this)),
1359
+ model.providers.map((provider) => /* @__PURE__ */ jsxDEV4("button", {
1360
+ disabled: snapshot.isRunning,
1361
+ onClick: () => run(provider.provider, "recovery"),
1362
+ type: "button",
1363
+ children: [
1364
+ "Mark ",
1365
+ provider.provider,
1366
+ " recovered"
1367
+ ]
1368
+ }, `recover-${provider.provider}`, true, undefined, this))
1369
+ ]
1370
+ }, undefined, true, undefined, this),
1371
+ snapshot.error ? /* @__PURE__ */ jsxDEV4("p", {
1372
+ className: "absolute-voice-provider-simulation__error",
1373
+ children: snapshot.error
1374
+ }, undefined, false, undefined, this) : null,
1375
+ model.resultText ? /* @__PURE__ */ jsxDEV4("pre", {
1376
+ className: "absolute-voice-provider-simulation__result",
1377
+ children: model.resultText
1378
+ }, undefined, false, undefined, this) : null
1379
+ ]
1380
+ }, undefined, true, undefined, this);
1381
+ };
1382
+ // src/react/useVoiceProviderCapabilities.tsx
1383
+ import { useEffect as useEffect5, useRef as useRef5, useSyncExternalStore as useSyncExternalStore5 } from "react";
1384
+
1385
+ // src/client/providerCapabilities.ts
1386
+ var fetchVoiceProviderCapabilities = async (path = "/api/provider-capabilities", options = {}) => {
1387
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1388
+ const response = await fetchImpl(path);
1389
+ if (!response.ok) {
1390
+ throw new Error(`Voice provider capabilities failed: HTTP ${response.status}`);
1391
+ }
1392
+ return await response.json();
1393
+ };
1394
+ var createVoiceProviderCapabilitiesStore = (path = "/api/provider-capabilities", options = {}) => {
1395
+ const listeners = new Set;
1396
+ let closed = false;
1397
+ let timer;
1398
+ let snapshot = {
1399
+ error: null,
1400
+ isLoading: false
1401
+ };
1402
+ const emit = () => {
1403
+ for (const listener of listeners) {
1404
+ listener();
1405
+ }
1406
+ };
1407
+ const refresh = async () => {
1408
+ if (closed) {
1409
+ return snapshot.report;
1410
+ }
1411
+ snapshot = {
1412
+ ...snapshot,
1413
+ error: null,
1414
+ isLoading: true
1415
+ };
1416
+ emit();
1417
+ try {
1418
+ const report = await fetchVoiceProviderCapabilities(path, options);
1419
+ snapshot = {
1420
+ error: null,
1421
+ isLoading: false,
1422
+ report,
1423
+ updatedAt: Date.now()
1424
+ };
1425
+ emit();
1426
+ return report;
1427
+ } catch (error) {
1428
+ snapshot = {
1429
+ ...snapshot,
1430
+ error: error instanceof Error ? error.message : String(error),
1431
+ isLoading: false
1432
+ };
1433
+ emit();
1434
+ throw error;
1435
+ }
1436
+ };
1437
+ const close = () => {
1438
+ closed = true;
1439
+ if (timer) {
1440
+ clearInterval(timer);
1441
+ timer = undefined;
1442
+ }
1443
+ listeners.clear();
1444
+ };
1445
+ if (options.intervalMs && options.intervalMs > 0) {
1446
+ timer = setInterval(() => {
1447
+ refresh().catch(() => {});
1448
+ }, options.intervalMs);
1449
+ }
1450
+ return {
1451
+ close,
1452
+ getServerSnapshot: () => snapshot,
1453
+ getSnapshot: () => snapshot,
1454
+ refresh,
1455
+ subscribe: (listener) => {
1456
+ listeners.add(listener);
1457
+ return () => {
1458
+ listeners.delete(listener);
1459
+ };
1460
+ }
1461
+ };
1462
+ };
1463
+
1464
+ // src/react/useVoiceProviderCapabilities.tsx
1465
+ var useVoiceProviderCapabilities = (path = "/api/provider-capabilities", options = {}) => {
1466
+ const storeRef = useRef5(null);
1467
+ if (!storeRef.current) {
1468
+ storeRef.current = createVoiceProviderCapabilitiesStore(path, options);
1469
+ }
1470
+ const store = storeRef.current;
1471
+ useEffect5(() => {
1472
+ store.refresh().catch(() => {});
1473
+ return () => store.close();
1474
+ }, [store]);
1475
+ return {
1476
+ ...useSyncExternalStore5(store.subscribe, store.getSnapshot, store.getServerSnapshot),
1477
+ refresh: store.refresh
1478
+ };
1479
+ };
1480
+
1481
+ // src/client/providerCapabilitiesWidget.ts
1482
+ var DEFAULT_TITLE4 = "Provider Capabilities";
1483
+ var DEFAULT_DESCRIPTION4 = "Configured, selected, and healthy voice providers for this deployment.";
1484
+ var escapeHtml5 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1485
+ var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
1486
+ var formatKind2 = (kind) => kind.toUpperCase();
1487
+ var formatStatus = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
1488
+ var getCapabilityDetail = (capability) => {
1489
+ if (!capability.configured) {
1490
+ return "Not configured in this deployment.";
1491
+ }
1492
+ if (capability.selected) {
1493
+ return `Selected ${capability.kind.toUpperCase()} provider for new sessions.`;
1494
+ }
1495
+ if (capability.health?.status === "healthy") {
1496
+ return "Configured and healthy fallback candidate.";
1497
+ }
1498
+ if (capability.health?.status === "idle") {
1499
+ return "Configured; no traffic observed yet.";
1500
+ }
1501
+ if (capability.health?.lastError) {
1502
+ return capability.health.lastError;
1503
+ }
1504
+ return "Configured and available.";
1505
+ };
1506
+ var isWarningStatus = (status) => status === "degraded" || status === "rate-limited" || status === "suppressed" || status === "unconfigured";
1507
+ var createVoiceProviderCapabilitiesViewModel = (snapshot, options = {}) => {
1508
+ const capabilities = (snapshot.report?.capabilities ?? []).map((capability) => ({
1509
+ ...capability,
1510
+ detail: getCapabilityDetail(capability),
1511
+ label: `${formatProvider(capability.provider)} ${formatKind2(capability.kind)}`,
1512
+ rows: [
1513
+ { label: "Status", value: formatStatus(capability.status) },
1514
+ { label: "Selected", value: capability.selected ? "Yes" : "No" },
1515
+ { label: "Model", value: capability.model ?? "Default" },
1516
+ {
1517
+ label: "Features",
1518
+ value: capability.features?.join(", ") || "Not specified"
1519
+ },
1520
+ { label: "Runs", value: String(capability.health?.runCount ?? 0) },
1521
+ { label: "Errors", value: String(capability.health?.errorCount ?? 0) }
1522
+ ]
1523
+ }));
1524
+ const warningCount = capabilities.filter((capability) => isWarningStatus(capability.status)).length;
1525
+ const selectedCount = snapshot.report?.selected ?? capabilities.filter((capability) => capability.selected).length;
1526
+ return {
1527
+ capabilities,
1528
+ description: options.description ?? DEFAULT_DESCRIPTION4,
1529
+ error: snapshot.error,
1530
+ isLoading: snapshot.isLoading,
1531
+ label: snapshot.error ? "Unavailable" : capabilities.length ? warningCount > 0 ? `${warningCount} needs attention` : `${selectedCount} selected` : snapshot.isLoading ? "Checking" : "No capabilities",
1532
+ status: snapshot.error ? "error" : capabilities.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
1533
+ title: options.title ?? DEFAULT_TITLE4,
1534
+ updatedAt: snapshot.updatedAt
1535
+ };
1536
+ };
1537
+ var renderVoiceProviderCapabilitiesHTML = (snapshot, options = {}) => {
1538
+ const model = createVoiceProviderCapabilitiesViewModel(snapshot, options);
1539
+ 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--${escapeHtml5(capability.status)}">
1540
+ <header>
1541
+ <strong>${escapeHtml5(capability.label)}</strong>
1542
+ <span>${escapeHtml5(formatStatus(capability.status))}</span>
1543
+ </header>
1544
+ <p>${escapeHtml5(capability.detail)}</p>
1545
+ <dl>${capability.rows.map((row) => `<div>
1546
+ <dt>${escapeHtml5(row.label)}</dt>
1547
+ <dd>${escapeHtml5(row.value)}</dd>
1548
+ </div>`).join("")}</dl>
1549
+ </article>`).join("")}</div>` : '<p class="absolute-voice-provider-capabilities__empty">Configure provider capabilities to see deployment coverage.</p>';
1550
+ return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml5(model.status)}">
1551
+ <header class="absolute-voice-provider-capabilities__header">
1552
+ <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml5(model.title)}</span>
1553
+ <strong class="absolute-voice-provider-capabilities__label">${escapeHtml5(model.label)}</strong>
1554
+ </header>
1555
+ <p class="absolute-voice-provider-capabilities__description">${escapeHtml5(model.description)}</p>
1556
+ ${capabilities}
1557
+ ${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml5(model.error)}</p>` : ""}
1558
+ </section>`;
1559
+ };
1560
+ 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}`;
1561
+ var mountVoiceProviderCapabilities = (element, path = "/api/provider-capabilities", options = {}) => {
1562
+ const store = createVoiceProviderCapabilitiesStore(path, options);
1563
+ const render = () => {
1564
+ element.innerHTML = renderVoiceProviderCapabilitiesHTML(store.getSnapshot(), options);
1565
+ };
1566
+ const unsubscribe = store.subscribe(render);
1567
+ render();
1568
+ store.refresh().catch(() => {});
1569
+ return {
1570
+ close: () => {
1571
+ unsubscribe();
1572
+ store.close();
1573
+ },
1574
+ refresh: store.refresh
1575
+ };
1576
+ };
1577
+ var defineVoiceProviderCapabilitiesElement = (tagName = "absolute-voice-provider-capabilities") => {
1578
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
1579
+ return;
1580
+ }
1581
+ customElements.define(tagName, class AbsoluteVoiceProviderCapabilitiesElement extends HTMLElement {
1582
+ mounted;
1583
+ connectedCallback() {
1584
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
1585
+ this.mounted = mountVoiceProviderCapabilities(this, this.getAttribute("path") ?? "/api/provider-capabilities", {
1586
+ description: this.getAttribute("description") ?? undefined,
1587
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
1588
+ title: this.getAttribute("title") ?? undefined
1589
+ });
1590
+ }
1591
+ disconnectedCallback() {
1592
+ this.mounted?.close();
1593
+ this.mounted = undefined;
1594
+ }
1595
+ });
1596
+ };
1597
+
1598
+ // src/react/VoiceProviderCapabilities.tsx
1599
+ import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
1600
+ var VoiceProviderCapabilities = ({
1601
+ className,
1602
+ path = "/api/provider-capabilities",
1603
+ ...options
1604
+ }) => {
1605
+ const snapshot = useVoiceProviderCapabilities(path, options);
1606
+ const model = createVoiceProviderCapabilitiesViewModel(snapshot, options);
1607
+ return /* @__PURE__ */ jsxDEV5("section", {
1608
+ className: [
1609
+ "absolute-voice-provider-capabilities",
1610
+ `absolute-voice-provider-capabilities--${model.status}`,
1611
+ className
1612
+ ].filter(Boolean).join(" "),
1613
+ children: [
1614
+ /* @__PURE__ */ jsxDEV5("header", {
1615
+ className: "absolute-voice-provider-capabilities__header",
1616
+ children: [
1617
+ /* @__PURE__ */ jsxDEV5("span", {
1618
+ className: "absolute-voice-provider-capabilities__eyebrow",
1619
+ children: model.title
1620
+ }, undefined, false, undefined, this),
1621
+ /* @__PURE__ */ jsxDEV5("strong", {
1622
+ className: "absolute-voice-provider-capabilities__label",
1623
+ children: model.label
1624
+ }, undefined, false, undefined, this)
1625
+ ]
1626
+ }, undefined, true, undefined, this),
1627
+ /* @__PURE__ */ jsxDEV5("p", {
1628
+ className: "absolute-voice-provider-capabilities__description",
1629
+ children: model.description
1630
+ }, undefined, false, undefined, this),
1631
+ model.capabilities.length ? /* @__PURE__ */ jsxDEV5("div", {
1632
+ className: "absolute-voice-provider-capabilities__providers",
1633
+ children: model.capabilities.map((capability) => /* @__PURE__ */ jsxDEV5("article", {
1634
+ className: [
1635
+ "absolute-voice-provider-capabilities__provider",
1636
+ `absolute-voice-provider-capabilities__provider--${capability.status}`
1637
+ ].join(" "),
1638
+ children: [
1639
+ /* @__PURE__ */ jsxDEV5("header", {
1640
+ children: [
1641
+ /* @__PURE__ */ jsxDEV5("strong", {
1642
+ children: capability.label
1643
+ }, undefined, false, undefined, this),
1644
+ /* @__PURE__ */ jsxDEV5("span", {
1645
+ children: capability.status
1646
+ }, undefined, false, undefined, this)
1647
+ ]
1648
+ }, undefined, true, undefined, this),
1649
+ /* @__PURE__ */ jsxDEV5("p", {
1650
+ children: capability.detail
1651
+ }, undefined, false, undefined, this),
1652
+ /* @__PURE__ */ jsxDEV5("dl", {
1653
+ children: capability.rows.map((row) => /* @__PURE__ */ jsxDEV5("div", {
1654
+ children: [
1655
+ /* @__PURE__ */ jsxDEV5("dt", {
1656
+ children: row.label
1657
+ }, undefined, false, undefined, this),
1658
+ /* @__PURE__ */ jsxDEV5("dd", {
1659
+ children: row.value
1660
+ }, undefined, false, undefined, this)
1661
+ ]
1662
+ }, row.label, true, undefined, this))
1663
+ }, undefined, false, undefined, this)
1664
+ ]
1665
+ }, `${capability.kind}:${capability.provider}`, true, undefined, this))
1666
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV5("p", {
1667
+ className: "absolute-voice-provider-capabilities__empty",
1668
+ children: "Configure provider capabilities to see deployment coverage."
1669
+ }, undefined, false, undefined, this),
1670
+ model.error ? /* @__PURE__ */ jsxDEV5("p", {
1671
+ className: "absolute-voice-provider-capabilities__error",
1672
+ children: model.error
1673
+ }, undefined, false, undefined, this) : null
1674
+ ]
1675
+ }, undefined, true, undefined, this);
1676
+ };
1677
+ // src/react/useVoiceProviderContracts.tsx
1678
+ import { useEffect as useEffect6, useRef as useRef6, useSyncExternalStore as useSyncExternalStore6 } from "react";
1679
+
1680
+ // src/client/providerContracts.ts
1681
+ var fetchVoiceProviderContracts = async (path = "/api/provider-contracts", options = {}) => {
1682
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1683
+ const response = await fetchImpl(path);
1684
+ if (!response.ok) {
1685
+ throw new Error(`Voice provider contracts failed: HTTP ${response.status}`);
1686
+ }
1687
+ return await response.json();
1688
+ };
1689
+ var createVoiceProviderContractsStore = (path = "/api/provider-contracts", options = {}) => {
1690
+ const listeners = new Set;
1691
+ let closed = false;
1692
+ let timer;
1693
+ let snapshot = {
1694
+ error: null,
1695
+ isLoading: false
1696
+ };
1697
+ const emit = () => {
1698
+ for (const listener of listeners) {
1699
+ listener();
1700
+ }
1701
+ };
1702
+ const refresh = async () => {
1703
+ if (closed) {
1704
+ return snapshot.report;
1705
+ }
1706
+ snapshot = { ...snapshot, error: null, isLoading: true };
1707
+ emit();
1708
+ try {
1709
+ const report = await fetchVoiceProviderContracts(path, options);
1710
+ snapshot = {
1711
+ error: null,
1712
+ isLoading: false,
1713
+ report,
1714
+ updatedAt: Date.now()
1715
+ };
1716
+ emit();
1717
+ return report;
1718
+ } catch (error) {
1719
+ snapshot = {
1720
+ ...snapshot,
1721
+ error: error instanceof Error ? error.message : String(error),
1722
+ isLoading: false
1723
+ };
1724
+ emit();
1725
+ throw error;
1726
+ }
1727
+ };
1728
+ const close = () => {
1729
+ closed = true;
1730
+ if (timer) {
1731
+ clearInterval(timer);
1732
+ timer = undefined;
1733
+ }
1734
+ listeners.clear();
1735
+ };
1736
+ if (options.intervalMs && options.intervalMs > 0) {
1737
+ timer = setInterval(() => {
1738
+ refresh().catch(() => {});
1739
+ }, options.intervalMs);
1740
+ }
1741
+ return {
1742
+ close,
1743
+ getServerSnapshot: () => snapshot,
1744
+ getSnapshot: () => snapshot,
1745
+ refresh,
1746
+ subscribe: (listener) => {
1747
+ listeners.add(listener);
1748
+ return () => {
1749
+ listeners.delete(listener);
1750
+ };
1751
+ }
1752
+ };
1753
+ };
1754
+
1755
+ // src/react/useVoiceProviderContracts.tsx
1756
+ var useVoiceProviderContracts = (path = "/api/provider-contracts", options = {}) => {
1757
+ const storeRef = useRef6(null);
1758
+ if (!storeRef.current) {
1759
+ storeRef.current = createVoiceProviderContractsStore(path, options);
1760
+ }
1761
+ const store = storeRef.current;
1762
+ useEffect6(() => {
1763
+ store.refresh().catch(() => {});
1764
+ return () => store.close();
1765
+ }, [store]);
1766
+ return {
1767
+ ...useSyncExternalStore6(store.subscribe, store.getSnapshot, store.getServerSnapshot),
1768
+ refresh: store.refresh
1769
+ };
1770
+ };
1771
+
1772
+ // src/client/providerContractsWidget.ts
1773
+ var DEFAULT_TITLE5 = "Provider Contracts";
1774
+ var DEFAULT_DESCRIPTION5 = "Production contract coverage for provider env, latency, fallback, streaming, and capabilities.";
1775
+ var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1776
+ var formatProvider2 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
1777
+ var formatStatus2 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
1778
+ var contractDetail = (row) => {
1779
+ const failing = row.checks.filter((check) => check.status !== "pass");
1780
+ if (failing.length === 0) {
1781
+ return "Provider contract is production-ready.";
1782
+ }
1783
+ return failing.map((check) => `${check.label}: ${check.detail ?? check.status}`).join(" ");
1784
+ };
1785
+ var createVoiceProviderContractsViewModel = (snapshot, options = {}) => {
1786
+ const rows = (snapshot.report?.rows ?? []).map((row) => ({
1787
+ ...row,
1788
+ detail: contractDetail(row),
1789
+ label: `${formatProvider2(row.provider)} ${row.kind.toUpperCase()}`,
1790
+ remediations: row.checks.filter((check) => check.status !== "pass" && check.remediation).map((check) => ({
1791
+ detail: check.remediation?.detail ?? "",
1792
+ href: check.remediation?.href,
1793
+ label: check.remediation?.label ?? check.label
1794
+ })),
1795
+ rows: [
1796
+ { label: "Status", value: formatStatus2(row.status) },
1797
+ { label: "Selected", value: row.selected ? "Yes" : "No" },
1798
+ { label: "Configured", value: row.configured ? "Yes" : "No" },
1799
+ {
1800
+ label: "Checks",
1801
+ value: row.checks.map((check) => `${check.label}: ${formatStatus2(check.status)}`).join(", ")
1802
+ }
1803
+ ]
1804
+ }));
1805
+ const warningCount = snapshot.report ? snapshot.report.failed + snapshot.report.warned : rows.filter((row) => row.status !== "pass").length;
1806
+ return {
1807
+ description: options.description ?? DEFAULT_DESCRIPTION5,
1808
+ error: snapshot.error,
1809
+ isLoading: snapshot.isLoading,
1810
+ label: snapshot.error ? "Unavailable" : rows.length ? warningCount > 0 ? `${warningCount} needs attention` : `${rows.length} passing` : snapshot.isLoading ? "Checking" : "No contracts",
1811
+ rows,
1812
+ status: snapshot.error ? "error" : rows.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
1813
+ title: options.title ?? DEFAULT_TITLE5,
1814
+ updatedAt: snapshot.updatedAt
1815
+ };
1816
+ };
1817
+ var renderVoiceProviderContractsHTML = (snapshot, options = {}) => {
1818
+ const model = createVoiceProviderContractsViewModel(snapshot, options);
1819
+ const rows = model.rows.length ? `<div class="absolute-voice-provider-contracts__rows">${model.rows.map((row) => `<article class="absolute-voice-provider-contracts__row absolute-voice-provider-contracts__row--${escapeHtml6(row.status)}">
1820
+ <header>
1821
+ <strong>${escapeHtml6(row.label)}</strong>
1822
+ <span>${escapeHtml6(formatStatus2(row.status))}</span>
1823
+ </header>
1824
+ <p>${escapeHtml6(row.detail)}</p>
1825
+ ${row.remediations.length ? `<ul class="absolute-voice-provider-contracts__remediations">${row.remediations.map((remediation) => `<li>${remediation.href ? `<a href="${escapeHtml6(remediation.href)}">${escapeHtml6(remediation.label)}</a>` : `<strong>${escapeHtml6(remediation.label)}</strong>`}<span>${escapeHtml6(remediation.detail)}</span></li>`).join("")}</ul>` : ""}
1826
+ <dl>${row.rows.map((item) => `<div>
1827
+ <dt>${escapeHtml6(item.label)}</dt>
1828
+ <dd>${escapeHtml6(item.value)}</dd>
1829
+ </div>`).join("")}</dl>
1830
+ </article>`).join("")}</div>` : '<p class="absolute-voice-provider-contracts__empty">Configure provider contracts to see production coverage.</p>';
1831
+ return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${escapeHtml6(model.status)}">
1832
+ <header class="absolute-voice-provider-contracts__header">
1833
+ <span class="absolute-voice-provider-contracts__eyebrow">${escapeHtml6(model.title)}</span>
1834
+ <strong class="absolute-voice-provider-contracts__label">${escapeHtml6(model.label)}</strong>
1835
+ </header>
1836
+ <p class="absolute-voice-provider-contracts__description">${escapeHtml6(model.description)}</p>
1837
+ ${rows}
1838
+ ${model.error ? `<p class="absolute-voice-provider-contracts__error">${escapeHtml6(model.error)}</p>` : ""}
1839
+ </section>`;
1840
+ };
1841
+ var getVoiceProviderContractsCSS = () => `.absolute-voice-provider-contracts{border:1px solid #b8dcc7;border-radius:20px;background:#f7fff9;color:#09140d;padding:18px;box-shadow:0 18px 40px rgba(21,83,45,.12);font-family:inherit}.absolute-voice-provider-contracts--error,.absolute-voice-provider-contracts--warning{border-color:#f2a7a7;background:#fff7f4}.absolute-voice-provider-contracts__header,.absolute-voice-provider-contracts__row header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-provider-contracts__eyebrow{color:#166534;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-provider-contracts__label{font-size:24px;line-height:1}.absolute-voice-provider-contracts__description,.absolute-voice-provider-contracts__row p,.absolute-voice-provider-contracts__row dt,.absolute-voice-provider-contracts__empty{color:#405448}.absolute-voice-provider-contracts__rows{display:grid;gap:12px;margin-top:14px}.absolute-voice-provider-contracts__row{background:#fff;border:1px solid #d6eadb;border-radius:16px;padding:14px}.absolute-voice-provider-contracts__row--pass{border-color:#86efac}.absolute-voice-provider-contracts__row--warn,.absolute-voice-provider-contracts__row--fail{border-color:#f2a7a7}.absolute-voice-provider-contracts__row p{margin:10px 0}.absolute-voice-provider-contracts__remediations{display:grid;gap:8px;list-style:none;margin:0 0 10px;padding:0}.absolute-voice-provider-contracts__remediations li{background:#fff7ed;border:1px solid #fed7aa;border-radius:12px;display:grid;gap:3px;padding:8px}.absolute-voice-provider-contracts__remediations a,.absolute-voice-provider-contracts__remediations strong{color:#9a3412}.absolute-voice-provider-contracts__remediations span{color:#7c2d12}.absolute-voice-provider-contracts__row dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-provider-contracts__row div{background:#f7fff9;border:1px solid #d6eadb;border-radius:12px;padding:8px}.absolute-voice-provider-contracts__row dt{font-size:12px}.absolute-voice-provider-contracts__row dd{font-weight:800;margin:4px 0 0}.absolute-voice-provider-contracts__error{color:#9f1239;font-weight:700}`;
1842
+ var mountVoiceProviderContracts = (element, path = "/api/provider-contracts", options = {}) => {
1843
+ const store = createVoiceProviderContractsStore(path, options);
1844
+ const render = () => {
1845
+ element.innerHTML = renderVoiceProviderContractsHTML(store.getSnapshot(), options);
1846
+ };
1847
+ const unsubscribe = store.subscribe(render);
1848
+ render();
1849
+ store.refresh().catch(() => {});
1850
+ return {
1851
+ close: () => {
1852
+ unsubscribe();
1853
+ store.close();
1854
+ },
1855
+ refresh: store.refresh
1856
+ };
1857
+ };
1858
+ var defineVoiceProviderContractsElement = (tagName = "absolute-voice-provider-contracts") => {
1859
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
1860
+ return;
1861
+ }
1862
+ customElements.define(tagName, class AbsoluteVoiceProviderContractsElement extends HTMLElement {
1863
+ mounted;
1864
+ connectedCallback() {
1865
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
1866
+ this.mounted = mountVoiceProviderContracts(this, this.getAttribute("path") ?? "/api/provider-contracts", {
1867
+ description: this.getAttribute("description") ?? undefined,
1868
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
1869
+ title: this.getAttribute("title") ?? undefined
1870
+ });
1871
+ }
1872
+ disconnectedCallback() {
1873
+ this.mounted?.close();
1874
+ this.mounted = undefined;
1875
+ }
1876
+ });
1877
+ };
1878
+
1879
+ // src/react/VoiceProviderContracts.tsx
1880
+ import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
1881
+ var VoiceProviderContracts = ({
1882
+ className,
1883
+ path = "/api/provider-contracts",
1884
+ ...options
1885
+ }) => {
1886
+ const snapshot = useVoiceProviderContracts(path, options);
1887
+ const model = createVoiceProviderContractsViewModel(snapshot, options);
1888
+ return /* @__PURE__ */ jsxDEV6("section", {
1889
+ className: [
1890
+ "absolute-voice-provider-contracts",
1891
+ `absolute-voice-provider-contracts--${model.status}`,
1892
+ className
1893
+ ].filter(Boolean).join(" "),
1894
+ children: [
1895
+ /* @__PURE__ */ jsxDEV6("header", {
1896
+ className: "absolute-voice-provider-contracts__header",
1897
+ children: [
1898
+ /* @__PURE__ */ jsxDEV6("span", {
1899
+ className: "absolute-voice-provider-contracts__eyebrow",
1900
+ children: model.title
1901
+ }, undefined, false, undefined, this),
1902
+ /* @__PURE__ */ jsxDEV6("strong", {
1903
+ className: "absolute-voice-provider-contracts__label",
1904
+ children: model.label
1905
+ }, undefined, false, undefined, this)
1906
+ ]
1907
+ }, undefined, true, undefined, this),
1908
+ /* @__PURE__ */ jsxDEV6("p", {
1909
+ className: "absolute-voice-provider-contracts__description",
1910
+ children: model.description
1911
+ }, undefined, false, undefined, this),
1912
+ model.rows.length ? /* @__PURE__ */ jsxDEV6("div", {
1913
+ className: "absolute-voice-provider-contracts__rows",
1914
+ children: model.rows.map((row) => /* @__PURE__ */ jsxDEV6("article", {
1915
+ className: [
1916
+ "absolute-voice-provider-contracts__row",
1917
+ `absolute-voice-provider-contracts__row--${row.status}`
1918
+ ].join(" "),
1919
+ children: [
1920
+ /* @__PURE__ */ jsxDEV6("header", {
1921
+ children: [
1922
+ /* @__PURE__ */ jsxDEV6("strong", {
1923
+ children: row.label
1924
+ }, undefined, false, undefined, this),
1925
+ /* @__PURE__ */ jsxDEV6("span", {
1926
+ children: row.status
1927
+ }, undefined, false, undefined, this)
1928
+ ]
1929
+ }, undefined, true, undefined, this),
1930
+ /* @__PURE__ */ jsxDEV6("p", {
1931
+ children: row.detail
1932
+ }, undefined, false, undefined, this),
1933
+ row.remediations.length ? /* @__PURE__ */ jsxDEV6("ul", {
1934
+ className: "absolute-voice-provider-contracts__remediations",
1935
+ children: row.remediations.map((remediation) => /* @__PURE__ */ jsxDEV6("li", {
1936
+ children: [
1937
+ remediation.href ? /* @__PURE__ */ jsxDEV6("a", {
1938
+ href: remediation.href,
1939
+ children: remediation.label
1940
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV6("strong", {
1941
+ children: remediation.label
1942
+ }, undefined, false, undefined, this),
1943
+ /* @__PURE__ */ jsxDEV6("span", {
1944
+ children: remediation.detail
1945
+ }, undefined, false, undefined, this)
1946
+ ]
1947
+ }, `${row.kind}:${row.provider}:${remediation.label}`, true, undefined, this))
1948
+ }, undefined, false, undefined, this) : null,
1949
+ /* @__PURE__ */ jsxDEV6("dl", {
1950
+ children: row.rows.map((item) => /* @__PURE__ */ jsxDEV6("div", {
1951
+ children: [
1952
+ /* @__PURE__ */ jsxDEV6("dt", {
1953
+ children: item.label
1954
+ }, undefined, false, undefined, this),
1955
+ /* @__PURE__ */ jsxDEV6("dd", {
1956
+ children: item.value
1957
+ }, undefined, false, undefined, this)
1958
+ ]
1959
+ }, item.label, true, undefined, this))
1960
+ }, undefined, false, undefined, this)
1961
+ ]
1962
+ }, `${row.kind}:${row.provider}`, true, undefined, this))
1963
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV6("p", {
1964
+ className: "absolute-voice-provider-contracts__empty",
1965
+ children: "Configure provider contracts to see production coverage."
1966
+ }, undefined, false, undefined, this),
1967
+ model.error ? /* @__PURE__ */ jsxDEV6("p", {
1968
+ className: "absolute-voice-provider-contracts__error",
1969
+ children: model.error
1970
+ }, undefined, false, undefined, this) : null
1971
+ ]
1972
+ }, undefined, true, undefined, this);
1973
+ };
1974
+ // src/react/useVoiceProviderStatus.tsx
1975
+ import { useEffect as useEffect7, useRef as useRef7, useSyncExternalStore as useSyncExternalStore7 } from "react";
1976
+
1977
+ // src/client/providerStatus.ts
1978
+ var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
1979
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1980
+ const response = await fetchImpl(path);
1981
+ if (!response.ok) {
1982
+ throw new Error(`Voice provider status failed: HTTP ${response.status}`);
1983
+ }
1984
+ return await response.json();
1985
+ };
1986
+ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
1987
+ const listeners = new Set;
1988
+ let closed = false;
1989
+ let timer;
1990
+ let snapshot = {
1991
+ error: null,
1992
+ isLoading: false,
1993
+ providers: []
1994
+ };
1995
+ const emit = () => {
1996
+ for (const listener of listeners) {
1997
+ listener();
1998
+ }
1999
+ };
2000
+ const refresh = async () => {
2001
+ if (closed) {
2002
+ return snapshot.providers;
2003
+ }
2004
+ snapshot = {
2005
+ ...snapshot,
2006
+ error: null,
2007
+ isLoading: true
2008
+ };
2009
+ emit();
2010
+ try {
2011
+ const providers = await fetchVoiceProviderStatus(path, options);
2012
+ snapshot = {
2013
+ error: null,
2014
+ isLoading: false,
2015
+ providers,
2016
+ updatedAt: Date.now()
2017
+ };
2018
+ emit();
2019
+ return providers;
2020
+ } catch (error) {
2021
+ snapshot = {
2022
+ ...snapshot,
2023
+ error: error instanceof Error ? error.message : String(error),
2024
+ isLoading: false
2025
+ };
2026
+ emit();
2027
+ throw error;
2028
+ }
2029
+ };
2030
+ const close = () => {
2031
+ closed = true;
2032
+ if (timer) {
2033
+ clearInterval(timer);
2034
+ timer = undefined;
2035
+ }
2036
+ listeners.clear();
2037
+ };
2038
+ if (options.intervalMs && options.intervalMs > 0) {
2039
+ timer = setInterval(() => {
2040
+ refresh().catch(() => {});
2041
+ }, options.intervalMs);
2042
+ }
2043
+ return {
2044
+ close,
2045
+ getServerSnapshot: () => snapshot,
2046
+ getSnapshot: () => snapshot,
2047
+ refresh,
2048
+ subscribe: (listener) => {
2049
+ listeners.add(listener);
2050
+ return () => {
2051
+ listeners.delete(listener);
2052
+ };
2053
+ }
2054
+ };
2055
+ };
2056
+
2057
+ // src/react/useVoiceProviderStatus.tsx
2058
+ var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
2059
+ const storeRef = useRef7(null);
2060
+ if (!storeRef.current) {
2061
+ storeRef.current = createVoiceProviderStatusStore(path, options);
2062
+ }
2063
+ const store = storeRef.current;
2064
+ useEffect7(() => {
2065
+ store.refresh().catch(() => {});
2066
+ return () => store.close();
2067
+ }, [store]);
2068
+ return {
2069
+ ...useSyncExternalStore7(store.subscribe, store.getSnapshot, store.getServerSnapshot),
2070
+ refresh: store.refresh
2071
+ };
2072
+ };
2073
+
2074
+ // src/client/providerStatusWidget.ts
2075
+ var DEFAULT_TITLE6 = "Voice Providers";
2076
+ var DEFAULT_DESCRIPTION6 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
2077
+ var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2078
+ var formatProvider3 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
2079
+ var formatStatus3 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
2080
+ var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
2081
+ var formatSuppression = (value) => typeof value === "number" ? `${Math.ceil(value / 1000)}s` : "None";
2082
+ var getProviderDetail = (provider) => {
2083
+ if (provider.status === "suppressed") {
2084
+ return provider.lastError ? `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)} after ${provider.lastError}.` : `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)}.`;
2085
+ }
2086
+ if (provider.status === "recoverable") {
2087
+ return "Cooldown expired; ready for recovery traffic.";
2088
+ }
2089
+ if (provider.status === "rate-limited") {
2090
+ return "Rate limit detected; router should avoid this provider.";
2091
+ }
2092
+ if (provider.status === "degraded") {
2093
+ return provider.lastError ?? "Recent provider errors detected.";
2094
+ }
2095
+ if (provider.status === "healthy") {
2096
+ return provider.recommended ? "Healthy and currently recommended." : "Healthy and available for routing.";
2097
+ }
2098
+ return "No provider traffic observed yet.";
2099
+ };
2100
+ var isWarningStatus2 = (status) => status === "degraded" || status === "rate-limited" || status === "recoverable" || status === "suppressed";
2101
+ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
2102
+ const providers = snapshot.providers.map((provider) => ({
2103
+ ...provider,
2104
+ detail: getProviderDetail(provider),
2105
+ label: `${formatProvider3(provider.provider)}${provider.recommended ? " recommended" : ""}`,
2106
+ rows: [
2107
+ { label: "Runs", value: String(provider.runCount) },
2108
+ { label: "Avg latency", value: formatLatency(provider.averageElapsedMs) },
2109
+ { label: "Errors", value: String(provider.errorCount) },
2110
+ { label: "Timeouts", value: String(provider.timeoutCount) },
2111
+ { label: "Fallbacks", value: String(provider.fallbackCount) },
2112
+ {
2113
+ label: "Suppression",
2114
+ value: formatSuppression(provider.suppressionRemainingMs)
2115
+ }
2116
+ ]
2117
+ }));
2118
+ const warningCount = providers.filter((provider) => isWarningStatus2(provider.status)).length;
2119
+ const healthyCount = providers.filter((provider) => provider.status === "healthy").length;
2120
+ return {
2121
+ description: options.description ?? DEFAULT_DESCRIPTION6,
2122
+ error: snapshot.error,
2123
+ isLoading: snapshot.isLoading,
2124
+ label: snapshot.error ? "Unavailable" : providers.length ? warningCount > 0 ? `${warningCount} needs attention` : `${healthyCount} healthy` : snapshot.isLoading ? "Checking" : "No provider traffic",
2125
+ providers,
2126
+ status: snapshot.error ? "error" : providers.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
2127
+ title: options.title ?? DEFAULT_TITLE6,
2128
+ updatedAt: snapshot.updatedAt
2129
+ };
2130
+ };
2131
+ var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
2132
+ const model = createVoiceProviderStatusViewModel(snapshot, options);
2133
+ 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)}">
2134
+ <header>
2135
+ <strong>${escapeHtml7(provider.label)}</strong>
2136
+ <span>${escapeHtml7(formatStatus3(provider.status))}</span>
2137
+ </header>
2138
+ <p>${escapeHtml7(provider.detail)}</p>
2139
+ <dl>${provider.rows.map((row) => `<div>
2140
+ <dt>${escapeHtml7(row.label)}</dt>
2141
+ <dd>${escapeHtml7(row.value)}</dd>
2142
+ </div>`).join("")}</dl>
2143
+ </article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
2144
+ return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml7(model.status)}">
2145
+ <header class="absolute-voice-provider-status__header">
2146
+ <span class="absolute-voice-provider-status__eyebrow">${escapeHtml7(model.title)}</span>
2147
+ <strong class="absolute-voice-provider-status__label">${escapeHtml7(model.label)}</strong>
2148
+ </header>
2149
+ <p class="absolute-voice-provider-status__description">${escapeHtml7(model.description)}</p>
2150
+ ${providers}
2151
+ ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml7(model.error)}</p>` : ""}
2152
+ </section>`;
2153
+ };
2154
+ 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}`;
2155
+ var mountVoiceProviderStatus = (element, path = "/api/provider-status", options = {}) => {
2156
+ const store = createVoiceProviderStatusStore(path, options);
2157
+ const render = () => {
2158
+ element.innerHTML = renderVoiceProviderStatusHTML(store.getSnapshot(), options);
2159
+ };
2160
+ const unsubscribe = store.subscribe(render);
2161
+ render();
2162
+ store.refresh().catch(() => {});
2163
+ return {
2164
+ close: () => {
2165
+ unsubscribe();
2166
+ store.close();
2167
+ },
2168
+ refresh: store.refresh
2169
+ };
2170
+ };
2171
+ var defineVoiceProviderStatusElement = (tagName = "absolute-voice-provider-status") => {
2172
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
2173
+ return;
2174
+ }
2175
+ customElements.define(tagName, class AbsoluteVoiceProviderStatusElement extends HTMLElement {
2176
+ mounted;
2177
+ connectedCallback() {
2178
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
2179
+ this.mounted = mountVoiceProviderStatus(this, this.getAttribute("path") ?? "/api/provider-status", {
2180
+ description: this.getAttribute("description") ?? undefined,
2181
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
2182
+ title: this.getAttribute("title") ?? undefined
2183
+ });
2184
+ }
2185
+ disconnectedCallback() {
2186
+ this.mounted?.close();
2187
+ this.mounted = undefined;
2188
+ }
2189
+ });
2190
+ };
2191
+
2192
+ // src/react/VoiceProviderStatus.tsx
2193
+ import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
2194
+ var VoiceProviderStatus = ({
2195
+ className,
2196
+ path = "/api/provider-status",
2197
+ ...options
2198
+ }) => {
2199
+ const snapshot = useVoiceProviderStatus(path, options);
2200
+ const model = createVoiceProviderStatusViewModel(snapshot, options);
2201
+ return /* @__PURE__ */ jsxDEV7("section", {
2202
+ className: [
2203
+ "absolute-voice-provider-status",
2204
+ `absolute-voice-provider-status--${model.status}`,
2205
+ className
2206
+ ].filter(Boolean).join(" "),
2207
+ children: [
2208
+ /* @__PURE__ */ jsxDEV7("header", {
2209
+ className: "absolute-voice-provider-status__header",
2210
+ children: [
2211
+ /* @__PURE__ */ jsxDEV7("span", {
2212
+ className: "absolute-voice-provider-status__eyebrow",
2213
+ children: model.title
2214
+ }, undefined, false, undefined, this),
2215
+ /* @__PURE__ */ jsxDEV7("strong", {
2216
+ className: "absolute-voice-provider-status__label",
2217
+ children: model.label
2218
+ }, undefined, false, undefined, this)
2219
+ ]
2220
+ }, undefined, true, undefined, this),
2221
+ /* @__PURE__ */ jsxDEV7("p", {
2222
+ className: "absolute-voice-provider-status__description",
2223
+ children: model.description
2224
+ }, undefined, false, undefined, this),
2225
+ model.providers.length ? /* @__PURE__ */ jsxDEV7("div", {
2226
+ className: "absolute-voice-provider-status__providers",
2227
+ children: model.providers.map((provider) => /* @__PURE__ */ jsxDEV7("article", {
2228
+ className: [
2229
+ "absolute-voice-provider-status__provider",
2230
+ `absolute-voice-provider-status__provider--${provider.status}`
2231
+ ].join(" "),
2232
+ children: [
2233
+ /* @__PURE__ */ jsxDEV7("header", {
2234
+ children: [
2235
+ /* @__PURE__ */ jsxDEV7("strong", {
2236
+ children: provider.label
2237
+ }, undefined, false, undefined, this),
2238
+ /* @__PURE__ */ jsxDEV7("span", {
2239
+ children: provider.status
2240
+ }, undefined, false, undefined, this)
2241
+ ]
2242
+ }, undefined, true, undefined, this),
2243
+ /* @__PURE__ */ jsxDEV7("p", {
2244
+ children: provider.detail
2245
+ }, undefined, false, undefined, this),
2246
+ /* @__PURE__ */ jsxDEV7("dl", {
2247
+ children: provider.rows.map((row) => /* @__PURE__ */ jsxDEV7("div", {
2248
+ children: [
2249
+ /* @__PURE__ */ jsxDEV7("dt", {
2250
+ children: row.label
2251
+ }, undefined, false, undefined, this),
2252
+ /* @__PURE__ */ jsxDEV7("dd", {
2253
+ children: row.value
2254
+ }, undefined, false, undefined, this)
2255
+ ]
2256
+ }, row.label, true, undefined, this))
2257
+ }, undefined, false, undefined, this)
2258
+ ]
2259
+ }, provider.provider, true, undefined, this))
2260
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV7("p", {
2261
+ className: "absolute-voice-provider-status__empty",
2262
+ children: "Run voice traffic to see provider health."
2263
+ }, undefined, false, undefined, this),
2264
+ model.error ? /* @__PURE__ */ jsxDEV7("p", {
2265
+ className: "absolute-voice-provider-status__error",
2266
+ children: model.error
2267
+ }, undefined, false, undefined, this) : null
2268
+ ]
2269
+ }, undefined, true, undefined, this);
2270
+ };
2271
+ // src/react/useVoiceRoutingStatus.tsx
2272
+ import { useEffect as useEffect8, useRef as useRef8, useSyncExternalStore as useSyncExternalStore8 } from "react";
2273
+
2274
+ // src/client/routingStatus.ts
2275
+ var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
2276
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2277
+ const response = await fetchImpl(path);
2278
+ if (!response.ok) {
2279
+ throw new Error(`Voice routing status failed: HTTP ${response.status}`);
2280
+ }
2281
+ return await response.json();
2282
+ };
2283
+ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {}) => {
2284
+ const listeners = new Set;
2285
+ let closed = false;
2286
+ let timer;
2287
+ let snapshot = {
2288
+ decision: null,
2289
+ error: null,
2290
+ isLoading: false
2291
+ };
2292
+ const emit = () => {
2293
+ for (const listener of listeners) {
2294
+ listener();
2295
+ }
2296
+ };
2297
+ const refresh = async () => {
2298
+ if (closed) {
2299
+ return snapshot.decision;
2300
+ }
2301
+ snapshot = {
2302
+ ...snapshot,
2303
+ error: null,
2304
+ isLoading: true
2305
+ };
2306
+ emit();
2307
+ try {
2308
+ const decision = await fetchVoiceRoutingStatus(path, options);
2309
+ snapshot = {
2310
+ decision,
2311
+ error: null,
2312
+ isLoading: false,
2313
+ updatedAt: Date.now()
2314
+ };
2315
+ emit();
2316
+ return decision;
2317
+ } catch (error) {
2318
+ snapshot = {
2319
+ ...snapshot,
2320
+ error: error instanceof Error ? error.message : String(error),
2321
+ isLoading: false
2322
+ };
2323
+ emit();
2324
+ throw error;
2325
+ }
2326
+ };
2327
+ const close = () => {
2328
+ closed = true;
2329
+ if (timer) {
2330
+ clearInterval(timer);
2331
+ timer = undefined;
2332
+ }
2333
+ listeners.clear();
2334
+ };
2335
+ if (options.intervalMs && options.intervalMs > 0) {
2336
+ timer = setInterval(() => {
2337
+ refresh().catch(() => {});
2338
+ }, options.intervalMs);
2339
+ }
2340
+ return {
2341
+ close,
2342
+ getServerSnapshot: () => snapshot,
2343
+ getSnapshot: () => snapshot,
2344
+ refresh,
2345
+ subscribe: (listener) => {
2346
+ listeners.add(listener);
2347
+ return () => {
2348
+ listeners.delete(listener);
2349
+ };
2350
+ }
2351
+ };
2352
+ };
2353
+
2354
+ // src/react/useVoiceRoutingStatus.tsx
2355
+ var useVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
2356
+ const storeRef = useRef8(null);
2357
+ if (!storeRef.current) {
2358
+ storeRef.current = createVoiceRoutingStatusStore(path, options);
2359
+ }
2360
+ const store = storeRef.current;
2361
+ useEffect8(() => {
2362
+ store.refresh().catch(() => {});
2363
+ return () => store.close();
2364
+ }, [store]);
2365
+ return {
2366
+ ...useSyncExternalStore8(store.subscribe, store.getSnapshot, store.getServerSnapshot),
2367
+ refresh: store.refresh
2368
+ };
2369
+ };
2370
+
2371
+ // src/client/routingStatusWidget.ts
2372
+ var DEFAULT_TITLE7 = "Voice Routing";
2373
+ var DEFAULT_DESCRIPTION7 = "Latest provider routing decision from the self-hosted trace store.";
2374
+ var escapeHtml8 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2375
+ var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
2376
+ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
2377
+ const decision = snapshot.decision;
2378
+ const rows = decision ? [
2379
+ { label: "Kind", value: decision.kind.toUpperCase() },
2380
+ { label: "Policy", value: formatValue(decision.routing, "Unknown") },
2381
+ { label: "Provider", value: formatValue(decision.provider, "Unknown") },
2382
+ {
2383
+ label: "Selected",
2384
+ value: formatValue(decision.selectedProvider, "Unknown")
2385
+ },
2386
+ {
2387
+ label: "Fallback",
2388
+ value: formatValue(decision.fallbackProvider)
2389
+ },
2390
+ { label: "Status", value: formatValue(decision.status, "unknown") },
2391
+ {
2392
+ label: "Latency budget",
2393
+ value: typeof decision.latencyBudgetMs === "number" ? `${decision.latencyBudgetMs}ms` : "None"
2394
+ }
2395
+ ] : [];
2396
+ return {
2397
+ decision,
2398
+ description: options.description ?? DEFAULT_DESCRIPTION7,
2399
+ error: snapshot.error,
2400
+ isLoading: snapshot.isLoading,
2401
+ label: snapshot.error ? "Unavailable" : decision ? `${formatValue(decision.kind).toUpperCase()} ${formatValue(decision.status, "unknown")}` : snapshot.isLoading ? "Checking" : "No routing yet",
2402
+ rows,
2403
+ status: snapshot.error ? "error" : decision ? "ready" : snapshot.isLoading ? "loading" : "empty",
2404
+ title: options.title ?? DEFAULT_TITLE7,
2405
+ updatedAt: snapshot.updatedAt
2406
+ };
2407
+ };
2408
+ var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
2409
+ const model = createVoiceRoutingStatusViewModel(snapshot, options);
2410
+ const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
2411
+ <span>${escapeHtml8(row.label)}</span>
2412
+ <strong>${escapeHtml8(row.value)}</strong>
2413
+ </div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
2414
+ return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml8(model.status)}">
2415
+ <header class="absolute-voice-routing-status__header">
2416
+ <span class="absolute-voice-routing-status__eyebrow">${escapeHtml8(model.title)}</span>
2417
+ <strong class="absolute-voice-routing-status__label">${escapeHtml8(model.label)}</strong>
2418
+ </header>
2419
+ <p class="absolute-voice-routing-status__description">${escapeHtml8(model.description)}</p>
2420
+ ${rows}
2421
+ ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml8(model.error)}</p>` : ""}
2422
+ </section>`;
2423
+ };
2424
+ 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}`;
2425
+ var mountVoiceRoutingStatus = (element, path = "/api/routing/latest", options = {}) => {
2426
+ const store = createVoiceRoutingStatusStore(path, options);
2427
+ const render = () => {
2428
+ element.innerHTML = renderVoiceRoutingStatusHTML(store.getSnapshot(), options);
2429
+ };
2430
+ const unsubscribe = store.subscribe(render);
2431
+ render();
2432
+ store.refresh().catch(() => {});
2433
+ return {
2434
+ close: () => {
2435
+ unsubscribe();
2436
+ store.close();
2437
+ },
2438
+ refresh: store.refresh
2439
+ };
2440
+ };
2441
+ var defineVoiceRoutingStatusElement = (tagName = "absolute-voice-routing-status") => {
2442
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
2443
+ return;
2444
+ }
2445
+ customElements.define(tagName, class AbsoluteVoiceRoutingStatusElement extends HTMLElement {
2446
+ mounted;
2447
+ connectedCallback() {
2448
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
2449
+ this.mounted = mountVoiceRoutingStatus(this, this.getAttribute("path") ?? "/api/routing/latest", {
2450
+ description: this.getAttribute("description") ?? undefined,
2451
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
2452
+ title: this.getAttribute("title") ?? undefined
2453
+ });
2454
+ }
2455
+ disconnectedCallback() {
2456
+ this.mounted?.close();
2457
+ this.mounted = undefined;
2458
+ }
2459
+ });
2460
+ };
2461
+
2462
+ // src/react/VoiceRoutingStatus.tsx
2463
+ import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
2464
+ var VoiceRoutingStatus = ({
2465
+ className,
2466
+ path = "/api/routing/latest",
2467
+ ...options
2468
+ }) => {
2469
+ const snapshot = useVoiceRoutingStatus(path, options);
2470
+ const model = createVoiceRoutingStatusViewModel(snapshot, options);
2471
+ return /* @__PURE__ */ jsxDEV8("section", {
2472
+ className: [
2473
+ "absolute-voice-routing-status",
2474
+ `absolute-voice-routing-status--${model.status}`,
2475
+ className
2476
+ ].filter(Boolean).join(" "),
2477
+ children: [
2478
+ /* @__PURE__ */ jsxDEV8("header", {
2479
+ className: "absolute-voice-routing-status__header",
2480
+ children: [
2481
+ /* @__PURE__ */ jsxDEV8("span", {
2482
+ className: "absolute-voice-routing-status__eyebrow",
2483
+ children: model.title
2484
+ }, undefined, false, undefined, this),
2485
+ /* @__PURE__ */ jsxDEV8("strong", {
2486
+ className: "absolute-voice-routing-status__label",
2487
+ children: model.label
2488
+ }, undefined, false, undefined, this)
2489
+ ]
2490
+ }, undefined, true, undefined, this),
2491
+ /* @__PURE__ */ jsxDEV8("p", {
2492
+ className: "absolute-voice-routing-status__description",
2493
+ children: model.description
2494
+ }, undefined, false, undefined, this),
2495
+ model.rows.length ? /* @__PURE__ */ jsxDEV8("div", {
2496
+ className: "absolute-voice-routing-status__grid",
2497
+ children: model.rows.map((row) => /* @__PURE__ */ jsxDEV8("div", {
2498
+ children: [
2499
+ /* @__PURE__ */ jsxDEV8("span", {
2500
+ children: row.label
2501
+ }, undefined, false, undefined, this),
2502
+ /* @__PURE__ */ jsxDEV8("strong", {
2503
+ children: row.value
2504
+ }, undefined, false, undefined, this)
2505
+ ]
2506
+ }, row.label, true, undefined, this))
2507
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV8("p", {
2508
+ className: "absolute-voice-routing-status__empty",
2509
+ children: "Start a voice session to see the selected provider."
2510
+ }, undefined, false, undefined, this),
2511
+ model.error ? /* @__PURE__ */ jsxDEV8("p", {
2512
+ className: "absolute-voice-routing-status__error",
2513
+ children: model.error
2514
+ }, undefined, false, undefined, this) : null
2515
+ ]
2516
+ }, undefined, true, undefined, this);
2517
+ };
2518
+ // src/client/traceTimeline.ts
2519
+ var fetchVoiceTraceTimeline = async (path = "/api/voice-traces", options = {}) => {
2520
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2521
+ const response = await fetchImpl(path);
2522
+ if (!response.ok) {
2523
+ throw new Error(`Voice trace timeline failed: HTTP ${response.status}`);
2524
+ }
2525
+ return await response.json();
2526
+ };
2527
+ var createVoiceTraceTimelineStore = (path = "/api/voice-traces", options = {}) => {
2528
+ const listeners = new Set;
2529
+ let closed = false;
2530
+ let timer;
2531
+ let snapshot = {
2532
+ error: null,
2533
+ isLoading: false,
2534
+ report: null
2535
+ };
2536
+ const emit = () => {
2537
+ for (const listener of listeners) {
2538
+ listener();
2539
+ }
2540
+ };
2541
+ const refresh = async () => {
2542
+ if (closed) {
2543
+ return snapshot.report;
2544
+ }
2545
+ snapshot = {
2546
+ ...snapshot,
2547
+ error: null,
2548
+ isLoading: true
2549
+ };
2550
+ emit();
2551
+ try {
2552
+ const report = await fetchVoiceTraceTimeline(path, options);
2553
+ snapshot = {
2554
+ error: null,
2555
+ isLoading: false,
2556
+ report,
2557
+ updatedAt: Date.now()
2558
+ };
2559
+ emit();
2560
+ return report;
2561
+ } catch (error) {
2562
+ snapshot = {
2563
+ ...snapshot,
2564
+ error: error instanceof Error ? error.message : String(error),
2565
+ isLoading: false
2566
+ };
2567
+ emit();
2568
+ throw error;
2569
+ }
2570
+ };
2571
+ const close = () => {
2572
+ closed = true;
2573
+ if (timer) {
2574
+ clearInterval(timer);
2575
+ timer = undefined;
2576
+ }
2577
+ listeners.clear();
2578
+ };
2579
+ if (options.intervalMs && options.intervalMs > 0) {
2580
+ timer = setInterval(() => {
2581
+ refresh().catch(() => {});
2582
+ }, options.intervalMs);
2583
+ }
2584
+ return {
2585
+ close,
2586
+ getServerSnapshot: () => snapshot,
2587
+ getSnapshot: () => snapshot,
2588
+ refresh,
2589
+ subscribe: (listener) => {
2590
+ listeners.add(listener);
2591
+ return () => {
2592
+ listeners.delete(listener);
2593
+ };
2594
+ }
2595
+ };
2596
+ };
2597
+
2598
+ // src/client/traceTimelineWidget.ts
2599
+ var DEFAULT_TITLE8 = "Voice Traces";
2600
+ var DEFAULT_DESCRIPTION8 = "Latest call timelines with provider latency, fallbacks, handoffs, and errors from your self-hosted trace store.";
2601
+ var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2602
+ var formatMs = (value) => typeof value === "number" ? `${value}ms` : "n/a";
2603
+ var formatProviders = (session) => session.providers.length ? session.providers.map((provider) => provider.provider).join(", ") : "No providers";
2604
+ var createVoiceTraceTimelineViewModel = (snapshot, options = {}) => {
2605
+ const sessions = (snapshot.report?.sessions ?? []).slice(0, options.limit ?? 3).map((session) => ({
2606
+ ...session,
2607
+ detailHref: `${options.detailBasePath ?? "/traces"}/${encodeURIComponent(session.sessionId)}`,
2608
+ durationLabel: formatMs(session.summary.callDurationMs),
2609
+ label: `${session.summary.eventCount} events / ${session.summary.turnCount} turns`,
2610
+ providerLabel: formatProviders(session)
2611
+ }));
2612
+ const failed = sessions.filter((session) => session.status === "failed").length;
2613
+ const warnings = sessions.filter((session) => session.status === "warning").length;
2614
+ return {
2615
+ description: options.description ?? DEFAULT_DESCRIPTION8,
2616
+ error: snapshot.error,
2617
+ isLoading: snapshot.isLoading,
2618
+ label: snapshot.error ? "Unavailable" : failed > 0 ? `${failed} failed` : warnings > 0 ? `${warnings} warning` : sessions.length ? `${sessions.length} recent` : snapshot.isLoading ? "Checking" : "No traces yet",
2619
+ sessions,
2620
+ status: snapshot.error ? "error" : failed > 0 ? "failed" : warnings > 0 ? "warning" : sessions.length ? "ready" : snapshot.isLoading ? "loading" : "empty",
2621
+ title: options.title ?? DEFAULT_TITLE8,
2622
+ updatedAt: snapshot.updatedAt
2623
+ };
2624
+ };
2625
+ var renderVoiceTraceTimelineWidgetHTML = (snapshot, options = {}) => {
2626
+ const model = createVoiceTraceTimelineViewModel(snapshot, options);
2627
+ 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)}">
2628
+ <header>
2629
+ <strong>${escapeHtml9(session.sessionId)}</strong>
2630
+ <span>${escapeHtml9(session.status)}</span>
2631
+ </header>
2632
+ <p>${escapeHtml9(session.label)} \xB7 ${escapeHtml9(session.durationLabel)} \xB7 ${escapeHtml9(session.providerLabel)}</p>
2633
+ <a href="${escapeHtml9(session.detailHref)}">Open timeline</a>
2634
+ </article>`).join("")}</div>` : '<p class="absolute-voice-trace-timeline__empty">Run a voice session to see call timelines.</p>';
2635
+ return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml9(model.status)}">
2636
+ <header class="absolute-voice-trace-timeline__header">
2637
+ <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml9(model.title)}</span>
2638
+ <strong class="absolute-voice-trace-timeline__label">${escapeHtml9(model.label)}</strong>
2639
+ </header>
2640
+ <p class="absolute-voice-trace-timeline__description">${escapeHtml9(model.description)}</p>
2641
+ ${sessions}
2642
+ ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml9(model.error)}</p>` : ""}
2643
+ </section>`;
2644
+ };
2645
+ 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}`;
2646
+ var mountVoiceTraceTimeline = (element, path = "/api/voice-traces", options = {}) => {
2647
+ const store = createVoiceTraceTimelineStore(path, options);
2648
+ const render = () => {
2649
+ element.innerHTML = renderVoiceTraceTimelineWidgetHTML(store.getSnapshot(), options);
2650
+ };
2651
+ const unsubscribe = store.subscribe(render);
2652
+ render();
2653
+ store.refresh().catch(() => {});
2654
+ return {
2655
+ close: () => {
2656
+ unsubscribe();
2657
+ store.close();
2658
+ },
2659
+ refresh: store.refresh
2660
+ };
2661
+ };
2662
+ var defineVoiceTraceTimelineElement = (tagName = "absolute-voice-trace-timeline") => {
2663
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
2664
+ return;
2665
+ }
2666
+ customElements.define(tagName, class AbsoluteVoiceTraceTimelineElement extends HTMLElement {
2667
+ mounted;
2668
+ connectedCallback() {
2669
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
2670
+ const limit = Number(this.getAttribute("limit") ?? 3);
2671
+ this.mounted = mountVoiceTraceTimeline(this, this.getAttribute("path") ?? "/api/voice-traces", {
2672
+ description: this.getAttribute("description") ?? undefined,
2673
+ detailBasePath: this.getAttribute("detail-base-path") ?? undefined,
2674
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
2675
+ limit: Number.isFinite(limit) ? limit : 3,
2676
+ title: this.getAttribute("title") ?? undefined
2677
+ });
2678
+ }
2679
+ disconnectedCallback() {
2680
+ this.mounted?.close();
2681
+ this.mounted = undefined;
2682
+ }
2683
+ });
2684
+ };
2685
+
2686
+ // src/react/useVoiceTraceTimeline.tsx
2687
+ import { useEffect as useEffect9, useRef as useRef9, useSyncExternalStore as useSyncExternalStore9 } from "react";
2688
+ var useVoiceTraceTimeline = (path = "/api/voice-traces", options = {}) => {
2689
+ const storeRef = useRef9(null);
2690
+ if (!storeRef.current) {
2691
+ storeRef.current = createVoiceTraceTimelineStore(path, options);
2692
+ }
2693
+ const store = storeRef.current;
2694
+ useEffect9(() => {
2695
+ store.refresh().catch(() => {});
2696
+ return () => store.close();
2697
+ }, [store]);
2698
+ return {
2699
+ ...useSyncExternalStore9(store.subscribe, store.getSnapshot, store.getServerSnapshot),
2700
+ refresh: store.refresh
2701
+ };
2702
+ };
2703
+
2704
+ // src/react/VoiceTraceTimeline.tsx
2705
+ import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
2706
+ var VoiceTraceTimeline = ({
2707
+ className,
2708
+ path = "/api/voice-traces",
2709
+ ...options
2710
+ }) => {
2711
+ const snapshot = useVoiceTraceTimeline(path, options);
2712
+ const model = createVoiceTraceTimelineViewModel(snapshot, options);
2713
+ return /* @__PURE__ */ jsxDEV9("section", {
2714
+ className: [
2715
+ "absolute-voice-trace-timeline",
2716
+ `absolute-voice-trace-timeline--${model.status}`,
2717
+ className
2718
+ ].filter(Boolean).join(" "),
2719
+ children: [
2720
+ /* @__PURE__ */ jsxDEV9("header", {
2721
+ className: "absolute-voice-trace-timeline__header",
2722
+ children: [
2723
+ /* @__PURE__ */ jsxDEV9("span", {
2724
+ className: "absolute-voice-trace-timeline__eyebrow",
2725
+ children: model.title
2726
+ }, undefined, false, undefined, this),
2727
+ /* @__PURE__ */ jsxDEV9("strong", {
2728
+ className: "absolute-voice-trace-timeline__label",
2729
+ children: model.label
2730
+ }, undefined, false, undefined, this)
2731
+ ]
2732
+ }, undefined, true, undefined, this),
2733
+ /* @__PURE__ */ jsxDEV9("p", {
2734
+ className: "absolute-voice-trace-timeline__description",
2735
+ children: model.description
2736
+ }, undefined, false, undefined, this),
2737
+ model.sessions.length ? /* @__PURE__ */ jsxDEV9("div", {
2738
+ className: "absolute-voice-trace-timeline__sessions",
2739
+ children: model.sessions.map((session) => /* @__PURE__ */ jsxDEV9("article", {
2740
+ className: [
2741
+ "absolute-voice-trace-timeline__session",
2742
+ `absolute-voice-trace-timeline__session--${session.status}`
2743
+ ].join(" "),
2744
+ children: [
2745
+ /* @__PURE__ */ jsxDEV9("header", {
2746
+ children: [
2747
+ /* @__PURE__ */ jsxDEV9("strong", {
2748
+ children: session.sessionId
2749
+ }, undefined, false, undefined, this),
2750
+ /* @__PURE__ */ jsxDEV9("span", {
2751
+ children: session.status
2752
+ }, undefined, false, undefined, this)
2753
+ ]
2754
+ }, undefined, true, undefined, this),
2755
+ /* @__PURE__ */ jsxDEV9("p", {
2756
+ children: [
2757
+ session.label,
2758
+ " \xB7 ",
2759
+ session.durationLabel,
2760
+ " \xB7",
2761
+ " ",
2762
+ session.providerLabel
2763
+ ]
2764
+ }, undefined, true, undefined, this),
2765
+ /* @__PURE__ */ jsxDEV9("a", {
2766
+ href: session.detailHref,
2767
+ children: "Open timeline"
2768
+ }, undefined, false, undefined, this)
2769
+ ]
2770
+ }, session.sessionId, true, undefined, this))
2771
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV9("p", {
2772
+ className: "absolute-voice-trace-timeline__empty",
2773
+ children: "Run a voice session to see call timelines."
2774
+ }, undefined, false, undefined, this),
2775
+ model.error ? /* @__PURE__ */ jsxDEV9("p", {
2776
+ className: "absolute-voice-trace-timeline__error",
2777
+ children: model.error
2778
+ }, undefined, false, undefined, this) : null
2779
+ ]
2780
+ }, undefined, true, undefined, this);
2781
+ };
2782
+ // src/react/useVoiceTurnLatency.tsx
2783
+ import { useEffect as useEffect10, useRef as useRef10, useSyncExternalStore as useSyncExternalStore10 } from "react";
2784
+
2785
+ // src/client/turnLatency.ts
2786
+ var fetchVoiceTurnLatency = async (path = "/api/turn-latency", options = {}) => {
2787
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2788
+ const response = await fetchImpl(path);
2789
+ if (!response.ok) {
2790
+ throw new Error(`Voice turn latency failed: HTTP ${response.status}`);
2791
+ }
2792
+ return await response.json();
2793
+ };
2794
+ var runVoiceTurnLatencyProof = async (path, options = {}) => {
2795
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2796
+ const response = await fetchImpl(path, { method: "POST" });
2797
+ if (!response.ok) {
2798
+ throw new Error(`Voice turn latency proof failed: HTTP ${response.status}`);
2799
+ }
2800
+ return response.json();
2801
+ };
2802
+ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) => {
2803
+ const listeners = new Set;
2804
+ let closed = false;
2805
+ let timer;
2806
+ let snapshot = {
2807
+ error: null,
2808
+ isLoading: false
2809
+ };
2810
+ const emit = () => {
2811
+ for (const listener of listeners) {
2812
+ listener();
2813
+ }
2814
+ };
2815
+ const refresh = async () => {
2816
+ if (closed) {
2817
+ return snapshot.report;
2818
+ }
2819
+ snapshot = { ...snapshot, error: null, isLoading: true };
2820
+ emit();
2821
+ try {
2822
+ const report = await fetchVoiceTurnLatency(path, options);
2823
+ snapshot = {
2824
+ error: null,
2825
+ isLoading: false,
2826
+ report,
2827
+ updatedAt: Date.now()
2828
+ };
2829
+ emit();
2830
+ return report;
2831
+ } catch (error) {
2832
+ snapshot = {
2833
+ ...snapshot,
2834
+ error: error instanceof Error ? error.message : String(error),
2835
+ isLoading: false
2836
+ };
2837
+ emit();
2838
+ throw error;
2839
+ }
2840
+ };
2841
+ const runProof = async () => {
2842
+ if (!options.proofPath) {
2843
+ throw new Error("Voice turn latency proof path is not configured.");
2844
+ }
2845
+ snapshot = { ...snapshot, error: null, isLoading: true };
2846
+ emit();
2847
+ try {
2848
+ await runVoiceTurnLatencyProof(options.proofPath, options);
2849
+ return await refresh();
2850
+ } catch (error) {
2851
+ snapshot = {
2852
+ ...snapshot,
2853
+ error: error instanceof Error ? error.message : String(error),
2854
+ isLoading: false
2855
+ };
2856
+ emit();
2857
+ throw error;
2858
+ }
2859
+ };
2860
+ const close = () => {
2861
+ closed = true;
2862
+ if (timer) {
2863
+ clearInterval(timer);
2864
+ timer = undefined;
2865
+ }
2866
+ listeners.clear();
2867
+ };
2868
+ if (options.intervalMs && options.intervalMs > 0) {
2869
+ timer = setInterval(() => {
2870
+ refresh().catch(() => {});
2871
+ }, options.intervalMs);
2872
+ }
2873
+ return {
2874
+ close,
2875
+ getServerSnapshot: () => snapshot,
2876
+ getSnapshot: () => snapshot,
2877
+ refresh,
2878
+ runProof,
2879
+ subscribe: (listener) => {
2880
+ listeners.add(listener);
2881
+ return () => {
2882
+ listeners.delete(listener);
2883
+ };
2884
+ }
2885
+ };
2886
+ };
2887
+
2888
+ // src/react/useVoiceTurnLatency.tsx
2889
+ var useVoiceTurnLatency = (path = "/api/turn-latency", options = {}) => {
2890
+ const storeRef = useRef10(null);
2891
+ if (!storeRef.current) {
2892
+ storeRef.current = createVoiceTurnLatencyStore(path, options);
2893
+ }
2894
+ const store = storeRef.current;
2895
+ useEffect10(() => {
2896
+ store.refresh().catch(() => {});
2897
+ return () => store.close();
2898
+ }, [store]);
2899
+ return {
2900
+ ...useSyncExternalStore10(store.subscribe, store.getSnapshot, store.getServerSnapshot),
2901
+ refresh: store.refresh,
2902
+ runProof: store.runProof
2903
+ };
2904
+ };
2905
+
2906
+ // src/client/turnLatencyWidget.ts
2907
+ var DEFAULT_TITLE9 = "Turn Latency";
2908
+ var DEFAULT_DESCRIPTION9 = "Per-turn timing from first transcript to commit and assistant response start.";
2909
+ var DEFAULT_PROOF_LABEL = "Run latency proof";
2910
+ var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2911
+ var formatMs2 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
2912
+ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
2913
+ const turns = (snapshot.report?.turns ?? []).map((turn) => ({
2914
+ ...turn,
2915
+ label: turn.text || "Empty turn",
2916
+ rows: turn.stages.map((stage) => ({
2917
+ label: stage.label,
2918
+ value: formatMs2(stage.valueMs)
2919
+ }))
2920
+ }));
2921
+ const warningCount = snapshot.report?.warnings ?? turns.filter((turn) => turn.status === "warn").length;
2922
+ const failedCount = snapshot.report?.failed ?? turns.filter((turn) => turn.status === "fail").length;
2923
+ return {
2924
+ description: options.description ?? DEFAULT_DESCRIPTION9,
2925
+ error: snapshot.error,
2926
+ isLoading: snapshot.isLoading,
2927
+ label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} slow` : warningCount > 0 ? `${warningCount} warnings` : `avg ${formatMs2(snapshot.report?.averageTotalMs)}` : snapshot.isLoading ? "Checking" : "No turns",
2928
+ proofLabel: options.proofPath ? options.proofLabel ?? DEFAULT_PROOF_LABEL : undefined,
2929
+ showProofAction: Boolean(options.proofPath),
2930
+ status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
2931
+ title: options.title ?? DEFAULT_TITLE9,
2932
+ turns,
2933
+ updatedAt: snapshot.updatedAt
2934
+ };
2935
+ };
2936
+ var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
2937
+ const model = createVoiceTurnLatencyViewModel(snapshot, options);
2938
+ 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)}">
2939
+ <header>
2940
+ <strong>${escapeHtml10(turn.label)}</strong>
2941
+ <span>${escapeHtml10(turn.status)}</span>
2942
+ </header>
2943
+ <dl>${turn.rows.map((row) => `<div>
2944
+ <dt>${escapeHtml10(row.label)}</dt>
2945
+ <dd>${escapeHtml10(row.value)}</dd>
2946
+ </div>`).join("")}</dl>
2947
+ </article>`).join("")}</div>` : '<p class="absolute-voice-turn-latency__empty">Complete a voice turn to see latency diagnostics.</p>';
2948
+ return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml10(model.status)}">
2949
+ <header class="absolute-voice-turn-latency__header">
2950
+ <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml10(model.title)}</span>
2951
+ <strong class="absolute-voice-turn-latency__label">${escapeHtml10(model.label)}</strong>
2952
+ </header>
2953
+ <p class="absolute-voice-turn-latency__description">${escapeHtml10(model.description)}</p>
2954
+ ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml10(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
2955
+ ${turns}
2956
+ ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml10(model.error)}</p>` : ""}
2957
+ </section>`;
2958
+ };
2959
+ var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {}) => {
2960
+ const store = createVoiceTurnLatencyStore(path, options);
2961
+ const render = () => {
2962
+ element.innerHTML = renderVoiceTurnLatencyHTML(store.getSnapshot(), options);
2963
+ };
2964
+ const handleClick = (event) => {
2965
+ const target = event.target;
2966
+ if (target instanceof Element && target.closest("[data-absolute-voice-turn-latency-proof]")) {
2967
+ store.runProof().catch(() => {});
2968
+ }
2969
+ };
2970
+ const unsubscribe = store.subscribe(render);
2971
+ element.addEventListener("click", handleClick);
2972
+ render();
2973
+ store.refresh().catch(() => {});
2974
+ return {
2975
+ close: () => {
2976
+ element.removeEventListener("click", handleClick);
2977
+ unsubscribe();
2978
+ store.close();
2979
+ },
2980
+ refresh: store.refresh
2981
+ };
2982
+ };
2983
+ var defineVoiceTurnLatencyElement = (tagName = "absolute-voice-turn-latency") => {
2984
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
2985
+ return;
2986
+ }
2987
+ customElements.define(tagName, class AbsoluteVoiceTurnLatencyElement extends HTMLElement {
2988
+ mounted;
2989
+ connectedCallback() {
2990
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
2991
+ this.mounted = mountVoiceTurnLatency(this, this.getAttribute("path") ?? "/api/turn-latency", {
2992
+ description: this.getAttribute("description") ?? undefined,
2993
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
2994
+ proofLabel: this.getAttribute("proof-label") ?? undefined,
2995
+ proofPath: this.getAttribute("proof-path") ?? undefined,
2996
+ title: this.getAttribute("title") ?? undefined
2997
+ });
2998
+ }
2999
+ disconnectedCallback() {
3000
+ this.mounted?.close();
3001
+ this.mounted = undefined;
3002
+ }
3003
+ });
3004
+ };
3005
+
3006
+ // src/react/VoiceTurnLatency.tsx
3007
+ import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
3008
+ var VoiceTurnLatency = ({
3009
+ className,
3010
+ path = "/api/turn-latency",
3011
+ ...options
3012
+ }) => {
3013
+ const latency = useVoiceTurnLatency(path, options);
3014
+ const model = createVoiceTurnLatencyViewModel(latency, options);
3015
+ return /* @__PURE__ */ jsxDEV10("section", {
3016
+ className: [
3017
+ "absolute-voice-turn-latency",
3018
+ `absolute-voice-turn-latency--${model.status}`,
3019
+ className
3020
+ ].filter(Boolean).join(" "),
3021
+ children: [
3022
+ /* @__PURE__ */ jsxDEV10("header", {
3023
+ className: "absolute-voice-turn-latency__header",
3024
+ children: [
3025
+ /* @__PURE__ */ jsxDEV10("span", {
3026
+ className: "absolute-voice-turn-latency__eyebrow",
3027
+ children: model.title
3028
+ }, undefined, false, undefined, this),
3029
+ /* @__PURE__ */ jsxDEV10("strong", {
3030
+ className: "absolute-voice-turn-latency__label",
3031
+ children: model.label
3032
+ }, undefined, false, undefined, this)
3033
+ ]
3034
+ }, undefined, true, undefined, this),
3035
+ /* @__PURE__ */ jsxDEV10("p", {
3036
+ className: "absolute-voice-turn-latency__description",
3037
+ children: model.description
3038
+ }, undefined, false, undefined, this),
3039
+ model.showProofAction ? /* @__PURE__ */ jsxDEV10("button", {
3040
+ className: "absolute-voice-turn-latency__proof",
3041
+ onClick: () => {
3042
+ latency.runProof().catch(() => {});
3043
+ },
3044
+ type: "button",
3045
+ children: model.proofLabel
3046
+ }, undefined, false, undefined, this) : null,
3047
+ model.turns.length ? /* @__PURE__ */ jsxDEV10("div", {
3048
+ className: "absolute-voice-turn-latency__turns",
3049
+ children: model.turns.map((turn) => /* @__PURE__ */ jsxDEV10("article", {
3050
+ className: [
3051
+ "absolute-voice-turn-latency__turn",
3052
+ `absolute-voice-turn-latency__turn--${turn.status}`
3053
+ ].join(" "),
3054
+ children: [
3055
+ /* @__PURE__ */ jsxDEV10("header", {
3056
+ children: [
3057
+ /* @__PURE__ */ jsxDEV10("strong", {
3058
+ children: turn.label
3059
+ }, undefined, false, undefined, this),
3060
+ /* @__PURE__ */ jsxDEV10("span", {
3061
+ children: turn.status
3062
+ }, undefined, false, undefined, this)
3063
+ ]
3064
+ }, undefined, true, undefined, this),
3065
+ /* @__PURE__ */ jsxDEV10("dl", {
3066
+ children: turn.rows.map((row) => /* @__PURE__ */ jsxDEV10("div", {
3067
+ children: [
3068
+ /* @__PURE__ */ jsxDEV10("dt", {
3069
+ children: row.label
3070
+ }, undefined, false, undefined, this),
3071
+ /* @__PURE__ */ jsxDEV10("dd", {
3072
+ children: row.value
3073
+ }, undefined, false, undefined, this)
3074
+ ]
3075
+ }, row.label, true, undefined, this))
3076
+ }, undefined, false, undefined, this)
3077
+ ]
3078
+ }, `${turn.sessionId}:${turn.turnId}`, true, undefined, this))
3079
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV10("p", {
3080
+ className: "absolute-voice-turn-latency__empty",
3081
+ children: "Complete a voice turn to see latency diagnostics."
3082
+ }, undefined, false, undefined, this),
3083
+ model.error ? /* @__PURE__ */ jsxDEV10("p", {
3084
+ className: "absolute-voice-turn-latency__error",
3085
+ children: model.error
3086
+ }, undefined, false, undefined, this) : null
3087
+ ]
3088
+ }, undefined, true, undefined, this);
3089
+ };
3090
+ // src/react/useVoiceTurnQuality.tsx
3091
+ import { useEffect as useEffect11, useRef as useRef11, useSyncExternalStore as useSyncExternalStore11 } from "react";
3092
+
3093
+ // src/client/turnQuality.ts
3094
+ var fetchVoiceTurnQuality = async (path = "/api/turn-quality", options = {}) => {
3095
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3096
+ const response = await fetchImpl(path);
3097
+ if (!response.ok) {
3098
+ throw new Error(`Voice turn quality failed: HTTP ${response.status}`);
3099
+ }
3100
+ return await response.json();
3101
+ };
3102
+ var createVoiceTurnQualityStore = (path = "/api/turn-quality", options = {}) => {
3103
+ const listeners = new Set;
3104
+ let closed = false;
3105
+ let timer;
3106
+ let snapshot = {
3107
+ error: null,
3108
+ isLoading: false
3109
+ };
3110
+ const emit = () => {
3111
+ for (const listener of listeners) {
3112
+ listener();
3113
+ }
3114
+ };
3115
+ const refresh = async () => {
3116
+ if (closed) {
3117
+ return snapshot.report;
3118
+ }
3119
+ snapshot = {
3120
+ ...snapshot,
3121
+ error: null,
3122
+ isLoading: true
3123
+ };
3124
+ emit();
3125
+ try {
3126
+ const report = await fetchVoiceTurnQuality(path, options);
3127
+ snapshot = {
3128
+ error: null,
3129
+ isLoading: false,
3130
+ report,
3131
+ updatedAt: Date.now()
3132
+ };
3133
+ emit();
3134
+ return report;
3135
+ } catch (error) {
3136
+ snapshot = {
3137
+ ...snapshot,
3138
+ error: error instanceof Error ? error.message : String(error),
3139
+ isLoading: false
3140
+ };
3141
+ emit();
3142
+ throw error;
3143
+ }
3144
+ };
3145
+ const close = () => {
3146
+ closed = true;
3147
+ if (timer) {
3148
+ clearInterval(timer);
3149
+ timer = undefined;
3150
+ }
3151
+ listeners.clear();
3152
+ };
3153
+ if (options.intervalMs && options.intervalMs > 0) {
3154
+ timer = setInterval(() => {
3155
+ refresh().catch(() => {});
3156
+ }, options.intervalMs);
3157
+ }
3158
+ return {
3159
+ close,
3160
+ getServerSnapshot: () => snapshot,
3161
+ getSnapshot: () => snapshot,
3162
+ refresh,
3163
+ subscribe: (listener) => {
3164
+ listeners.add(listener);
3165
+ return () => {
3166
+ listeners.delete(listener);
3167
+ };
3168
+ }
3169
+ };
3170
+ };
3171
+
3172
+ // src/react/useVoiceTurnQuality.tsx
3173
+ var useVoiceTurnQuality = (path = "/api/turn-quality", options = {}) => {
3174
+ const storeRef = useRef11(null);
3175
+ if (!storeRef.current) {
3176
+ storeRef.current = createVoiceTurnQualityStore(path, options);
3177
+ }
3178
+ const store = storeRef.current;
3179
+ useEffect11(() => {
3180
+ store.refresh().catch(() => {});
3181
+ return () => store.close();
3182
+ }, [store]);
3183
+ return {
3184
+ ...useSyncExternalStore11(store.subscribe, store.getSnapshot, store.getServerSnapshot),
3185
+ refresh: store.refresh
3186
+ };
3187
+ };
3188
+
3189
+ // src/client/turnQualityWidget.ts
3190
+ var DEFAULT_TITLE10 = "Turn Quality";
3191
+ var DEFAULT_DESCRIPTION10 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
3192
+ var escapeHtml11 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3193
+ var formatConfidence = (value) => typeof value === "number" ? `${Math.round(value * 100)}%` : "n/a";
3194
+ var formatMaybe = (value) => value === undefined || value === "" ? "n/a" : String(value);
3195
+ var getTurnDetail = (turn) => {
3196
+ if (turn.status === "fail") {
3197
+ return "Empty or unusable committed turn; inspect transcripts and adapter events.";
3198
+ }
3199
+ if (turn.fallbackUsed) {
3200
+ return `Fallback STT selected${turn.fallbackSelectionReason ? ` by ${turn.fallbackSelectionReason}` : ""}.`;
3201
+ }
3202
+ if (turn.correctionChanged) {
3203
+ return `Correction changed the turn${turn.correctionProvider ? ` via ${turn.correctionProvider}` : ""}.`;
3204
+ }
3205
+ if (turn.status === "warn") {
3206
+ return "Turn completed with quality warnings.";
3207
+ }
3208
+ if (turn.status === "unknown") {
3209
+ return "No quality diagnostics were recorded for this turn.";
3210
+ }
3211
+ return "Turn quality looks healthy.";
3212
+ };
3213
+ var createVoiceTurnQualityViewModel = (snapshot, options = {}) => {
3214
+ const turns = (snapshot.report?.turns ?? []).map((turn) => ({
3215
+ ...turn,
3216
+ detail: getTurnDetail(turn),
3217
+ label: turn.text || "Empty turn",
3218
+ rows: [
3219
+ { label: "Source", value: turn.source ?? "unknown" },
3220
+ { label: "Confidence", value: formatConfidence(turn.averageConfidence) },
3221
+ { label: "Fallback", value: turn.fallbackUsed ? "Yes" : "No" },
3222
+ { label: "Correction", value: turn.correctionChanged ? "Changed" : "None" },
3223
+ { label: "Transcripts", value: `${turn.selectedTranscriptCount} selected` },
3224
+ { label: "Cost", value: formatMaybe(turn.costUnits) }
3225
+ ]
3226
+ }));
3227
+ const warningCount = snapshot.report?.warnings ?? turns.filter((turn) => turn.status === "warn").length;
3228
+ const failedCount = snapshot.report?.failed ?? turns.filter((turn) => turn.status === "fail").length;
3229
+ return {
3230
+ description: options.description ?? DEFAULT_DESCRIPTION10,
3231
+ error: snapshot.error,
3232
+ isLoading: snapshot.isLoading,
3233
+ label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} failed` : warningCount > 0 ? `${warningCount} warnings` : `${turns.length} healthy` : snapshot.isLoading ? "Checking" : "No turns",
3234
+ status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
3235
+ title: options.title ?? DEFAULT_TITLE10,
3236
+ turns,
3237
+ updatedAt: snapshot.updatedAt
3238
+ };
3239
+ };
3240
+ var renderVoiceTurnQualityHTML = (snapshot, options = {}) => {
3241
+ const model = createVoiceTurnQualityViewModel(snapshot, options);
3242
+ 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--${escapeHtml11(turn.status)}">
3243
+ <header>
3244
+ <strong>${escapeHtml11(turn.label)}</strong>
3245
+ <span>${escapeHtml11(turn.status)}</span>
3246
+ </header>
3247
+ <p>${escapeHtml11(turn.detail)}</p>
3248
+ <dl>${turn.rows.map((row) => `<div>
3249
+ <dt>${escapeHtml11(row.label)}</dt>
3250
+ <dd>${escapeHtml11(row.value)}</dd>
3251
+ </div>`).join("")}</dl>
3252
+ </article>`).join("")}</div>` : '<p class="absolute-voice-turn-quality__empty">Complete a voice turn to see STT quality diagnostics.</p>';
3253
+ return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml11(model.status)}">
3254
+ <header class="absolute-voice-turn-quality__header">
3255
+ <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml11(model.title)}</span>
3256
+ <strong class="absolute-voice-turn-quality__label">${escapeHtml11(model.label)}</strong>
3257
+ </header>
3258
+ <p class="absolute-voice-turn-quality__description">${escapeHtml11(model.description)}</p>
3259
+ ${turns}
3260
+ ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml11(model.error)}</p>` : ""}
3261
+ </section>`;
3262
+ };
3263
+ 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}`;
3264
+ var mountVoiceTurnQuality = (element, path = "/api/turn-quality", options = {}) => {
3265
+ const store = createVoiceTurnQualityStore(path, options);
3266
+ const render = () => {
3267
+ element.innerHTML = renderVoiceTurnQualityHTML(store.getSnapshot(), options);
3268
+ };
3269
+ const unsubscribe = store.subscribe(render);
3270
+ render();
3271
+ store.refresh().catch(() => {});
3272
+ return {
3273
+ close: () => {
3274
+ unsubscribe();
3275
+ store.close();
3276
+ },
3277
+ refresh: store.refresh
3278
+ };
3279
+ };
3280
+ var defineVoiceTurnQualityElement = (tagName = "absolute-voice-turn-quality") => {
3281
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
3282
+ return;
3283
+ }
3284
+ customElements.define(tagName, class AbsoluteVoiceTurnQualityElement extends HTMLElement {
3285
+ mounted;
3286
+ connectedCallback() {
3287
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
3288
+ this.mounted = mountVoiceTurnQuality(this, this.getAttribute("path") ?? "/api/turn-quality", {
3289
+ description: this.getAttribute("description") ?? undefined,
3290
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
3291
+ title: this.getAttribute("title") ?? undefined
3292
+ });
3293
+ }
3294
+ disconnectedCallback() {
3295
+ this.mounted?.close();
3296
+ this.mounted = undefined;
3297
+ }
3298
+ });
3299
+ };
3300
+
3301
+ // src/react/VoiceTurnQuality.tsx
3302
+ import { jsxDEV as jsxDEV11 } from "react/jsx-dev-runtime";
3303
+ var VoiceTurnQuality = ({
3304
+ className,
3305
+ path = "/api/turn-quality",
3306
+ ...options
3307
+ }) => {
3308
+ const snapshot = useVoiceTurnQuality(path, options);
3309
+ const model = createVoiceTurnQualityViewModel(snapshot, options);
3310
+ return /* @__PURE__ */ jsxDEV11("section", {
3311
+ className: [
3312
+ "absolute-voice-turn-quality",
3313
+ `absolute-voice-turn-quality--${model.status}`,
3314
+ className
3315
+ ].filter(Boolean).join(" "),
3316
+ children: [
3317
+ /* @__PURE__ */ jsxDEV11("header", {
3318
+ className: "absolute-voice-turn-quality__header",
3319
+ children: [
3320
+ /* @__PURE__ */ jsxDEV11("span", {
3321
+ className: "absolute-voice-turn-quality__eyebrow",
3322
+ children: model.title
3323
+ }, undefined, false, undefined, this),
3324
+ /* @__PURE__ */ jsxDEV11("strong", {
3325
+ className: "absolute-voice-turn-quality__label",
3326
+ children: model.label
3327
+ }, undefined, false, undefined, this)
3328
+ ]
3329
+ }, undefined, true, undefined, this),
3330
+ /* @__PURE__ */ jsxDEV11("p", {
3331
+ className: "absolute-voice-turn-quality__description",
3332
+ children: model.description
3333
+ }, undefined, false, undefined, this),
3334
+ model.turns.length ? /* @__PURE__ */ jsxDEV11("div", {
3335
+ className: "absolute-voice-turn-quality__turns",
3336
+ children: model.turns.map((turn) => /* @__PURE__ */ jsxDEV11("article", {
3337
+ className: [
3338
+ "absolute-voice-turn-quality__turn",
3339
+ `absolute-voice-turn-quality__turn--${turn.status}`
3340
+ ].join(" "),
3341
+ children: [
3342
+ /* @__PURE__ */ jsxDEV11("header", {
3343
+ children: [
3344
+ /* @__PURE__ */ jsxDEV11("strong", {
3345
+ children: turn.label
3346
+ }, undefined, false, undefined, this),
3347
+ /* @__PURE__ */ jsxDEV11("span", {
3348
+ children: turn.status
3349
+ }, undefined, false, undefined, this)
3350
+ ]
3351
+ }, undefined, true, undefined, this),
3352
+ /* @__PURE__ */ jsxDEV11("p", {
3353
+ children: turn.detail
3354
+ }, undefined, false, undefined, this),
3355
+ /* @__PURE__ */ jsxDEV11("dl", {
3356
+ children: turn.rows.map((row) => /* @__PURE__ */ jsxDEV11("div", {
3357
+ children: [
3358
+ /* @__PURE__ */ jsxDEV11("dt", {
3359
+ children: row.label
3360
+ }, undefined, false, undefined, this),
3361
+ /* @__PURE__ */ jsxDEV11("dd", {
3362
+ children: row.value
3363
+ }, undefined, false, undefined, this)
3364
+ ]
3365
+ }, row.label, true, undefined, this))
3366
+ }, undefined, false, undefined, this)
3367
+ ]
3368
+ }, `${turn.sessionId}:${turn.turnId}`, true, undefined, this))
3369
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV11("p", {
3370
+ className: "absolute-voice-turn-quality__empty",
3371
+ children: "Complete a voice turn to see STT quality diagnostics."
3372
+ }, undefined, false, undefined, this),
3373
+ model.error ? /* @__PURE__ */ jsxDEV11("p", {
3374
+ className: "absolute-voice-turn-quality__error",
3375
+ children: model.error
3376
+ }, undefined, false, undefined, this) : null
3377
+ ]
3378
+ }, undefined, true, undefined, this);
3379
+ };
3380
+ // src/react/useVoiceCampaignDialerProof.tsx
3381
+ import { useEffect as useEffect12, useRef as useRef12, useSyncExternalStore as useSyncExternalStore12 } from "react";
3382
+
3383
+ // src/client/campaignDialerProof.ts
3384
+ var fetchVoiceCampaignDialerProofStatus = async (path = "/api/voice/campaigns/dialer-proof", options = {}) => {
3385
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3386
+ const response = await fetchImpl(path);
3387
+ if (!response.ok) {
3388
+ throw new Error(`Voice campaign dialer proof status failed: HTTP ${response.status}`);
3389
+ }
3390
+ return await response.json();
3391
+ };
3392
+ var runVoiceCampaignDialerProofAction = async (path = "/api/voice/campaigns/dialer-proof", options = {}) => {
3393
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3394
+ const response = await fetchImpl(path, { method: "POST" });
3395
+ if (!response.ok) {
3396
+ throw new Error(`Voice campaign dialer proof failed: HTTP ${response.status}`);
3397
+ }
3398
+ return await response.json();
3399
+ };
3400
+ var createVoiceCampaignDialerProofStore = (path = "/api/voice/campaigns/dialer-proof", options = {}) => {
3401
+ const listeners = new Set;
3402
+ let closed = false;
3403
+ let timer;
3404
+ let snapshot = {
3405
+ error: null,
3406
+ isLoading: false
3407
+ };
3408
+ const emit = () => {
3409
+ for (const listener of listeners) {
3410
+ listener();
3411
+ }
3412
+ };
3413
+ const refresh = async () => {
3414
+ if (closed) {
3415
+ return snapshot.status;
3416
+ }
3417
+ snapshot = { ...snapshot, error: null, isLoading: true };
3418
+ emit();
3419
+ try {
3420
+ const status = await fetchVoiceCampaignDialerProofStatus(path, options);
3421
+ snapshot = {
3422
+ ...snapshot,
3423
+ error: null,
3424
+ isLoading: false,
3425
+ status,
3426
+ updatedAt: Date.now()
3427
+ };
3428
+ emit();
3429
+ return status;
3430
+ } catch (error) {
3431
+ snapshot = {
3432
+ ...snapshot,
3433
+ error: error instanceof Error ? error.message : String(error),
3434
+ isLoading: false
3435
+ };
3436
+ emit();
3437
+ throw error;
3438
+ }
3439
+ };
3440
+ const runProof = async () => {
3441
+ const runPath = options.runPath ?? snapshot.status?.runPath ?? path;
3442
+ snapshot = { ...snapshot, error: null, isLoading: true };
3443
+ emit();
3444
+ try {
3445
+ const report = await runVoiceCampaignDialerProofAction(runPath, options);
3446
+ snapshot = {
3447
+ ...snapshot,
3448
+ error: null,
3449
+ isLoading: false,
3450
+ report,
3451
+ status: {
3452
+ generatedAt: Date.now(),
3453
+ mode: report.mode,
3454
+ ok: report.ok,
3455
+ providers: report.providers.map((provider) => provider.provider),
3456
+ runPath,
3457
+ safe: true
3458
+ },
3459
+ updatedAt: Date.now()
3460
+ };
3461
+ emit();
3462
+ return report;
3463
+ } catch (error) {
3464
+ snapshot = {
3465
+ ...snapshot,
3466
+ error: error instanceof Error ? error.message : String(error),
3467
+ isLoading: false
3468
+ };
3469
+ emit();
3470
+ throw error;
3471
+ }
3472
+ };
3473
+ const close = () => {
3474
+ closed = true;
3475
+ if (timer) {
3476
+ clearInterval(timer);
3477
+ timer = undefined;
3478
+ }
3479
+ listeners.clear();
3480
+ };
3481
+ if (options.intervalMs && options.intervalMs > 0) {
3482
+ timer = setInterval(() => {
3483
+ refresh().catch(() => {});
3484
+ }, options.intervalMs);
3485
+ }
3486
+ return {
3487
+ close,
3488
+ getServerSnapshot: () => snapshot,
3489
+ getSnapshot: () => snapshot,
3490
+ refresh,
3491
+ runProof,
3492
+ subscribe: (listener) => {
3493
+ listeners.add(listener);
3494
+ return () => {
3495
+ listeners.delete(listener);
3496
+ };
3497
+ }
3498
+ };
3499
+ };
3500
+
3501
+ // src/react/useVoiceCampaignDialerProof.tsx
3502
+ var useVoiceCampaignDialerProof = (path = "/api/voice/campaigns/dialer-proof", options = {}) => {
3503
+ const storeRef = useRef12(null);
3504
+ if (!storeRef.current) {
3505
+ storeRef.current = createVoiceCampaignDialerProofStore(path, options);
3506
+ }
3507
+ const store = storeRef.current;
3508
+ useEffect12(() => {
3509
+ store.refresh().catch(() => {});
3510
+ return () => store.close();
3511
+ }, [store]);
3512
+ return {
3513
+ ...useSyncExternalStore12(store.subscribe, store.getSnapshot, store.getServerSnapshot),
3514
+ refresh: store.refresh,
3515
+ runProof: store.runProof
3516
+ };
3517
+ };
72
3518
  // src/react/useVoiceStream.tsx
73
- import { useEffect, useRef, useSyncExternalStore } from "react";
3519
+ import { useEffect as useEffect13, useRef as useRef13, useSyncExternalStore as useSyncExternalStore13 } from "react";
74
3520
 
75
3521
  // src/client/actions.ts
76
3522
  var normalizeErrorMessage = (value) => {
@@ -120,6 +3566,17 @@ var serverMessageToAction = (message) => {
120
3566
  sessionId: message.sessionId,
121
3567
  type: "complete"
122
3568
  };
3569
+ case "connection":
3570
+ return {
3571
+ reconnect: message.reconnect,
3572
+ type: "connection"
3573
+ };
3574
+ case "call_lifecycle":
3575
+ return {
3576
+ event: message.event,
3577
+ sessionId: message.sessionId,
3578
+ type: "call_lifecycle"
3579
+ };
123
3580
  case "error":
124
3581
  return {
125
3582
  message: normalizeErrorMessage(message.message),
@@ -135,6 +3592,17 @@ var serverMessageToAction = (message) => {
135
3592
  transcript: message.transcript,
136
3593
  type: "partial"
137
3594
  };
3595
+ case "replay":
3596
+ return {
3597
+ assistantTexts: message.assistantTexts,
3598
+ call: message.call,
3599
+ partial: message.partial,
3600
+ scenarioId: message.scenarioId,
3601
+ sessionId: message.sessionId,
3602
+ status: message.status,
3603
+ turns: message.turns,
3604
+ type: "replay"
3605
+ };
138
3606
  case "session":
139
3607
  return {
140
3608
  sessionId: message.sessionId,
@@ -163,7 +3631,7 @@ var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
163
3631
  var noop = () => {};
164
3632
  var noopUnsubscribe = () => noop;
165
3633
  var NOOP_CONNECTION = {
166
- start: () => {},
3634
+ callControl: noop,
167
3635
  close: noop,
168
3636
  endTurn: noop,
169
3637
  getReadyState: () => WS_CLOSED,
@@ -171,6 +3639,7 @@ var NOOP_CONNECTION = {
171
3639
  getSessionId: () => "",
172
3640
  send: noop,
173
3641
  sendAudio: noop,
3642
+ start: () => {},
174
3643
  subscribe: noopUnsubscribe
175
3644
  };
176
3645
  var createSessionId = () => crypto.randomUUID();
@@ -192,11 +3661,14 @@ var isVoiceServerMessage = (value) => {
192
3661
  switch (value.type) {
193
3662
  case "audio":
194
3663
  case "assistant":
3664
+ case "call_lifecycle":
195
3665
  case "complete":
3666
+ case "connection":
196
3667
  case "error":
197
3668
  case "final":
198
3669
  case "partial":
199
3670
  case "pong":
3671
+ case "replay":
200
3672
  case "session":
201
3673
  case "turn":
202
3674
  return true;
@@ -233,6 +3705,9 @@ var createVoiceConnection = (path, options = {}) => {
233
3705
  sessionId: options.sessionId ?? createSessionId(),
234
3706
  ws: null
235
3707
  };
3708
+ const emitConnection = (reconnect) => {
3709
+ listeners.forEach((listener) => listener(reconnect));
3710
+ };
236
3711
  const clearTimers = () => {
237
3712
  if (state.pingInterval) {
238
3713
  clearInterval(state.pingInterval);
@@ -255,9 +3730,28 @@ var createVoiceConnection = (path, options = {}) => {
255
3730
  }
256
3731
  };
257
3732
  const scheduleReconnect = () => {
3733
+ const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
258
3734
  state.reconnectAttempts += 1;
3735
+ emitConnection({
3736
+ reconnect: {
3737
+ attempts: state.reconnectAttempts,
3738
+ lastDisconnectAt: Date.now(),
3739
+ maxAttempts: maxReconnectAttempts,
3740
+ nextAttemptAt,
3741
+ status: "reconnecting"
3742
+ },
3743
+ type: "connection"
3744
+ });
259
3745
  state.reconnectTimeout = setTimeout(() => {
260
3746
  if (state.reconnectAttempts > maxReconnectAttempts) {
3747
+ emitConnection({
3748
+ reconnect: {
3749
+ attempts: state.reconnectAttempts,
3750
+ maxAttempts: maxReconnectAttempts,
3751
+ status: "exhausted"
3752
+ },
3753
+ type: "connection"
3754
+ });
261
3755
  return;
262
3756
  }
263
3757
  connect();
@@ -267,9 +3761,21 @@ var createVoiceConnection = (path, options = {}) => {
267
3761
  const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
268
3762
  ws.binaryType = "arraybuffer";
269
3763
  ws.onopen = () => {
3764
+ const wasReconnecting = state.reconnectAttempts > 0;
270
3765
  state.isConnected = true;
271
- state.reconnectAttempts = 0;
272
3766
  flushPendingMessages();
3767
+ if (wasReconnecting) {
3768
+ emitConnection({
3769
+ reconnect: {
3770
+ attempts: state.reconnectAttempts,
3771
+ lastResumedAt: Date.now(),
3772
+ maxAttempts: maxReconnectAttempts,
3773
+ status: "resumed"
3774
+ },
3775
+ type: "connection"
3776
+ });
3777
+ state.reconnectAttempts = 0;
3778
+ }
273
3779
  listeners.forEach((listener) => listener({
274
3780
  scenarioId: state.scenarioId ?? undefined,
275
3781
  sessionId: state.sessionId,
@@ -299,6 +3805,16 @@ var createVoiceConnection = (path, options = {}) => {
299
3805
  const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
300
3806
  if (reconnectable) {
301
3807
  scheduleReconnect();
3808
+ } else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
3809
+ emitConnection({
3810
+ reconnect: {
3811
+ attempts: state.reconnectAttempts,
3812
+ lastDisconnectAt: Date.now(),
3813
+ maxAttempts: maxReconnectAttempts,
3814
+ status: "exhausted"
3815
+ },
3816
+ type: "connection"
3817
+ });
302
3818
  }
303
3819
  };
304
3820
  state.ws = ws;
@@ -332,6 +3848,12 @@ var createVoiceConnection = (path, options = {}) => {
332
3848
  const endTurn = () => {
333
3849
  send({ type: "end_turn" });
334
3850
  };
3851
+ const callControl = (message) => {
3852
+ send({
3853
+ ...message,
3854
+ type: "call_control"
3855
+ });
3856
+ };
335
3857
  const close = () => {
336
3858
  clearTimers();
337
3859
  if (state.ws) {
@@ -349,7 +3871,7 @@ var createVoiceConnection = (path, options = {}) => {
349
3871
  };
350
3872
  connect();
351
3873
  return {
352
- start,
3874
+ callControl,
353
3875
  close,
354
3876
  endTurn,
355
3877
  getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
@@ -357,18 +3879,26 @@ var createVoiceConnection = (path, options = {}) => {
357
3879
  getSessionId: () => state.sessionId,
358
3880
  send,
359
3881
  sendAudio,
3882
+ start,
360
3883
  subscribe
361
3884
  };
362
3885
  };
363
3886
 
364
3887
  // src/client/store.ts
3888
+ var createInitialReconnectState = () => ({
3889
+ attempts: 0,
3890
+ maxAttempts: 0,
3891
+ status: "idle"
3892
+ });
365
3893
  var createInitialState = () => ({
366
3894
  assistantAudio: [],
367
3895
  assistantTexts: [],
3896
+ call: null,
368
3897
  error: null,
369
3898
  isConnected: false,
370
3899
  scenarioId: null,
371
3900
  partial: "",
3901
+ reconnect: createInitialReconnectState(),
372
3902
  sessionId: null,
373
3903
  status: "idle",
374
3904
  turns: []
@@ -408,10 +3938,36 @@ var createVoiceStreamStore = () => {
408
3938
  status: "completed"
409
3939
  };
410
3940
  break;
3941
+ case "call_lifecycle":
3942
+ state = {
3943
+ ...state,
3944
+ call: {
3945
+ ...state.call,
3946
+ disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
3947
+ endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
3948
+ events: [...state.call?.events ?? [], action.event],
3949
+ lastEventAt: action.event.at,
3950
+ startedAt: state.call?.startedAt ?? action.event.at
3951
+ },
3952
+ sessionId: action.sessionId
3953
+ };
3954
+ break;
411
3955
  case "connected":
412
3956
  state = {
413
3957
  ...state,
414
- isConnected: true
3958
+ isConnected: true,
3959
+ reconnect: state.reconnect.status === "reconnecting" ? {
3960
+ ...state.reconnect,
3961
+ lastResumedAt: Date.now(),
3962
+ nextAttemptAt: undefined,
3963
+ status: "resumed"
3964
+ } : state.reconnect
3965
+ };
3966
+ break;
3967
+ case "connection":
3968
+ state = {
3969
+ ...state,
3970
+ reconnect: action.reconnect
415
3971
  };
416
3972
  break;
417
3973
  case "disconnected":
@@ -439,6 +3995,26 @@ var createVoiceStreamStore = () => {
439
3995
  partial: action.transcript.text
440
3996
  };
441
3997
  break;
3998
+ case "replay":
3999
+ state = {
4000
+ ...state,
4001
+ assistantTexts: [...action.assistantTexts],
4002
+ call: action.call ?? null,
4003
+ error: null,
4004
+ isConnected: action.status === "active",
4005
+ partial: action.partial,
4006
+ reconnect: state.reconnect.status === "reconnecting" ? {
4007
+ ...state.reconnect,
4008
+ lastResumedAt: Date.now(),
4009
+ nextAttemptAt: undefined,
4010
+ status: "resumed"
4011
+ } : state.reconnect,
4012
+ scenarioId: action.scenarioId ?? state.scenarioId,
4013
+ sessionId: action.sessionId,
4014
+ status: action.status,
4015
+ turns: [...action.turns]
4016
+ };
4017
+ break;
442
4018
  case "session":
443
4019
  state = {
444
4020
  ...state,
@@ -486,14 +4062,41 @@ var createVoiceStream = (path, options = {}) => {
486
4062
  const notify = () => {
487
4063
  subscribers.forEach((subscriber) => subscriber());
488
4064
  };
4065
+ const reportReconnect = () => {
4066
+ if (!options.reconnectReportPath || typeof fetch === "undefined") {
4067
+ return;
4068
+ }
4069
+ const snapshot = store.getSnapshot();
4070
+ const body = JSON.stringify({
4071
+ at: Date.now(),
4072
+ reconnect: snapshot.reconnect,
4073
+ scenarioId: snapshot.scenarioId,
4074
+ sessionId: connection.getSessionId(),
4075
+ turnIds: snapshot.turns.map((turn) => turn.id)
4076
+ });
4077
+ fetch(options.reconnectReportPath, {
4078
+ body,
4079
+ headers: {
4080
+ "Content-Type": "application/json"
4081
+ },
4082
+ keepalive: true,
4083
+ method: "POST"
4084
+ }).catch(() => {});
4085
+ };
489
4086
  const unsubscribeConnection = connection.subscribe((message) => {
490
4087
  const action = serverMessageToAction(message);
491
4088
  if (action) {
492
4089
  store.dispatch(action);
4090
+ if (message.type === "connection") {
4091
+ reportReconnect();
4092
+ }
493
4093
  notify();
494
4094
  }
495
4095
  });
496
4096
  return {
4097
+ callControl(message) {
4098
+ connection.callControl(message);
4099
+ },
497
4100
  close() {
498
4101
  unsubscribeConnection();
499
4102
  connection.close();
@@ -522,6 +4125,9 @@ var createVoiceStream = (path, options = {}) => {
522
4125
  get partial() {
523
4126
  return store.getSnapshot().partial;
524
4127
  },
4128
+ get reconnect() {
4129
+ return store.getSnapshot().reconnect;
4130
+ },
525
4131
  get sessionId() {
526
4132
  return connection.getSessionId();
527
4133
  },
@@ -537,6 +4143,9 @@ var createVoiceStream = (path, options = {}) => {
537
4143
  get assistantAudio() {
538
4144
  return store.getSnapshot().assistantAudio;
539
4145
  },
4146
+ get call() {
4147
+ return store.getSnapshot().call;
4148
+ },
540
4149
  sendAudio(audio) {
541
4150
  connection.sendAudio(audio);
542
4151
  },
@@ -553,30 +4162,37 @@ var createVoiceStream = (path, options = {}) => {
553
4162
  var EMPTY_SNAPSHOT = {
554
4163
  assistantAudio: [],
555
4164
  assistantTexts: [],
4165
+ call: null,
556
4166
  error: null,
557
4167
  isConnected: false,
558
4168
  partial: "",
4169
+ reconnect: {
4170
+ attempts: 0,
4171
+ maxAttempts: 0,
4172
+ status: "idle"
4173
+ },
559
4174
  sessionId: "",
560
4175
  status: "idle",
561
4176
  turns: []
562
4177
  };
563
4178
  var useVoiceStream = (path, options = {}) => {
564
- const streamRef = useRef(null);
4179
+ const streamRef = useRef13(null);
565
4180
  if (!streamRef.current) {
566
4181
  streamRef.current = createVoiceStream(path, options);
567
4182
  }
568
4183
  const stream = streamRef.current;
569
- useEffect(() => () => stream.close(), [stream]);
570
- const snapshot = useSyncExternalStore(stream.subscribe, stream.getSnapshot, stream.getServerSnapshot) ?? EMPTY_SNAPSHOT;
4184
+ useEffect13(() => () => stream.close(), [stream]);
4185
+ const snapshot = useSyncExternalStore13(stream.subscribe, stream.getSnapshot, stream.getServerSnapshot) ?? EMPTY_SNAPSHOT;
571
4186
  return {
572
4187
  ...snapshot,
4188
+ callControl: (message) => stream.callControl(message),
573
4189
  close: () => stream.close(),
574
4190
  endTurn: () => stream.endTurn(),
575
4191
  sendAudio: (audio) => stream.sendAudio(audio)
576
4192
  };
577
4193
  };
578
4194
  // src/react/useVoiceController.tsx
579
- import { useEffect as useEffect2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2 } from "react";
4195
+ import { useEffect as useEffect14, useRef as useRef14, useSyncExternalStore as useSyncExternalStore14 } from "react";
580
4196
 
581
4197
  // src/client/htmx.ts
582
4198
  var DEFAULT_EVENT_NAME = "voice-refresh";
@@ -1040,10 +4656,12 @@ var resolveVoiceRuntimePreset = (name = "default") => {
1040
4656
  var createInitialState2 = (stream) => ({
1041
4657
  assistantAudio: [...stream.assistantAudio],
1042
4658
  assistantTexts: [...stream.assistantTexts],
4659
+ call: stream.call,
1043
4660
  error: stream.error,
1044
4661
  isConnected: stream.isConnected,
1045
4662
  isRecording: false,
1046
4663
  partial: stream.partial,
4664
+ reconnect: stream.reconnect,
1047
4665
  recordingError: null,
1048
4666
  sessionId: stream.sessionId,
1049
4667
  scenarioId: stream.scenarioId,
@@ -1069,9 +4687,11 @@ var createVoiceController = (path, options = {}) => {
1069
4687
  ...state,
1070
4688
  assistantAudio: [...stream.assistantAudio],
1071
4689
  assistantTexts: [...stream.assistantTexts],
4690
+ call: stream.call,
1072
4691
  error: stream.error,
1073
4692
  isConnected: stream.isConnected,
1074
4693
  partial: stream.partial,
4694
+ reconnect: stream.reconnect,
1075
4695
  sessionId: stream.sessionId,
1076
4696
  scenarioId: stream.scenarioId,
1077
4697
  status: stream.status,
@@ -1096,7 +4716,13 @@ var createVoiceController = (path, options = {}) => {
1096
4716
  capture = createMicrophoneCapture({
1097
4717
  channelCount: options.capture?.channelCount ?? preset.capture.channelCount,
1098
4718
  onLevel: options.capture?.onLevel,
1099
- onAudio: (audio) => stream.sendAudio(audio),
4719
+ onAudio: (audio) => {
4720
+ if (options.capture?.onAudio) {
4721
+ options.capture.onAudio(audio, stream.sendAudio);
4722
+ return;
4723
+ }
4724
+ stream.sendAudio(audio);
4725
+ },
1100
4726
  sampleRateHz: options.capture?.sampleRateHz ?? preset.capture.sampleRateHz
1101
4727
  });
1102
4728
  return capture;
@@ -1146,6 +4772,7 @@ var createVoiceController = (path, options = {}) => {
1146
4772
  bindHTMX(bindingOptions) {
1147
4773
  return bindVoiceHTMX(stream, bindingOptions);
1148
4774
  },
4775
+ callControl: (message) => stream.callControl(message),
1149
4776
  close,
1150
4777
  endTurn: () => stream.endTurn(),
1151
4778
  get error() {
@@ -1165,6 +4792,9 @@ var createVoiceController = (path, options = {}) => {
1165
4792
  get recordingError() {
1166
4793
  return state.recordingError;
1167
4794
  },
4795
+ get reconnect() {
4796
+ return state.reconnect;
4797
+ },
1168
4798
  sendAudio: (audio) => stream.sendAudio(audio),
1169
4799
  get sessionId() {
1170
4800
  return state.sessionId;
@@ -1198,6 +4828,9 @@ var createVoiceController = (path, options = {}) => {
1198
4828
  },
1199
4829
  get assistantAudio() {
1200
4830
  return state.assistantAudio;
4831
+ },
4832
+ get call() {
4833
+ return state.call;
1201
4834
  }
1202
4835
  };
1203
4836
  };
@@ -1206,26 +4839,33 @@ var createVoiceController = (path, options = {}) => {
1206
4839
  var EMPTY_SNAPSHOT2 = {
1207
4840
  assistantAudio: [],
1208
4841
  assistantTexts: [],
4842
+ call: null,
1209
4843
  error: null,
1210
4844
  isConnected: false,
1211
4845
  isRecording: false,
1212
4846
  partial: "",
4847
+ reconnect: {
4848
+ attempts: 0,
4849
+ maxAttempts: 0,
4850
+ status: "idle"
4851
+ },
1213
4852
  recordingError: null,
1214
4853
  sessionId: "",
1215
4854
  status: "idle",
1216
4855
  turns: []
1217
4856
  };
1218
4857
  var useVoiceController = (path, options = {}) => {
1219
- const controllerRef = useRef2(null);
4858
+ const controllerRef = useRef14(null);
1220
4859
  if (!controllerRef.current) {
1221
4860
  controllerRef.current = createVoiceController(path, options);
1222
4861
  }
1223
4862
  const controller = controllerRef.current;
1224
- useEffect2(() => () => controller.close(), [controller]);
1225
- const snapshot = useSyncExternalStore2(controller.subscribe, controller.getSnapshot, controller.getServerSnapshot) ?? EMPTY_SNAPSHOT2;
4863
+ useEffect14(() => () => controller.close(), [controller]);
4864
+ const snapshot = useSyncExternalStore14(controller.subscribe, controller.getSnapshot, controller.getServerSnapshot) ?? EMPTY_SNAPSHOT2;
1226
4865
  return {
1227
4866
  ...snapshot,
1228
4867
  bindHTMX: controller.bindHTMX,
4868
+ callControl: (message) => controller.callControl(message),
1229
4869
  close: () => controller.close(),
1230
4870
  endTurn: () => controller.endTurn(),
1231
4871
  sendAudio: (audio) => controller.sendAudio(audio),
@@ -1234,26 +4874,25 @@ var useVoiceController = (path, options = {}) => {
1234
4874
  toggleRecording: () => controller.toggleRecording()
1235
4875
  };
1236
4876
  };
1237
- // src/react/useVoiceProviderStatus.tsx
1238
- import { useEffect as useEffect3, useRef as useRef3, useSyncExternalStore as useSyncExternalStore3 } from "react";
4877
+ // src/react/useVoiceWorkflowStatus.tsx
4878
+ import { useEffect as useEffect15, useRef as useRef15, useSyncExternalStore as useSyncExternalStore15 } from "react";
1239
4879
 
1240
- // src/client/providerStatus.ts
1241
- var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
4880
+ // src/client/workflowStatus.ts
4881
+ var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
1242
4882
  const fetchImpl = options.fetch ?? globalThis.fetch;
1243
4883
  const response = await fetchImpl(path);
1244
4884
  if (!response.ok) {
1245
- throw new Error(`Voice provider status failed: HTTP ${response.status}`);
4885
+ throw new Error(`Voice workflow status failed: HTTP ${response.status}`);
1246
4886
  }
1247
4887
  return await response.json();
1248
4888
  };
1249
- var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
4889
+ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options = {}) => {
1250
4890
  const listeners = new Set;
1251
4891
  let closed = false;
1252
4892
  let timer;
1253
4893
  let snapshot = {
1254
4894
  error: null,
1255
- isLoading: false,
1256
- providers: []
4895
+ isLoading: false
1257
4896
  };
1258
4897
  const emit = () => {
1259
4898
  for (const listener of listeners) {
@@ -1262,7 +4901,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1262
4901
  };
1263
4902
  const refresh = async () => {
1264
4903
  if (closed) {
1265
- return snapshot.providers;
4904
+ return snapshot.report;
1266
4905
  }
1267
4906
  snapshot = {
1268
4907
  ...snapshot,
@@ -1271,15 +4910,15 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1271
4910
  };
1272
4911
  emit();
1273
4912
  try {
1274
- const providers = await fetchVoiceProviderStatus(path, options);
4913
+ const report = await fetchVoiceWorkflowStatus(path, options);
1275
4914
  snapshot = {
1276
4915
  error: null,
1277
4916
  isLoading: false,
1278
- providers,
4917
+ report,
1279
4918
  updatedAt: Date.now()
1280
4919
  };
1281
4920
  emit();
1282
- return providers;
4921
+ return report;
1283
4922
  } catch (error) {
1284
4923
  snapshot = {
1285
4924
  ...snapshot,
@@ -1298,7 +4937,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1298
4937
  }
1299
4938
  listeners.clear();
1300
4939
  };
1301
- if (options.intervalMs && options.intervalMs > 0) {
4940
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
1302
4941
  timer = setInterval(() => {
1303
4942
  refresh().catch(() => {});
1304
4943
  }, options.intervalMs);
@@ -1317,24 +4956,47 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1317
4956
  };
1318
4957
  };
1319
4958
 
1320
- // src/react/useVoiceProviderStatus.tsx
1321
- var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
1322
- const storeRef = useRef3(null);
4959
+ // src/react/useVoiceWorkflowStatus.tsx
4960
+ var useVoiceWorkflowStatus = (path = "/evals/scenarios/json", options = {}) => {
4961
+ const storeRef = useRef15(null);
1323
4962
  if (!storeRef.current) {
1324
- storeRef.current = createVoiceProviderStatusStore(path, options);
4963
+ storeRef.current = createVoiceWorkflowStatusStore(path, options);
1325
4964
  }
1326
4965
  const store = storeRef.current;
1327
- useEffect3(() => {
4966
+ useEffect15(() => {
1328
4967
  store.refresh().catch(() => {});
1329
4968
  return () => store.close();
1330
4969
  }, [store]);
1331
4970
  return {
1332
- ...useSyncExternalStore3(store.subscribe, store.getSnapshot, store.getServerSnapshot),
4971
+ ...useSyncExternalStore15(store.subscribe, store.getSnapshot, store.getServerSnapshot),
1333
4972
  refresh: store.refresh
1334
4973
  };
1335
4974
  };
1336
4975
  export {
4976
+ useVoiceWorkflowStatus,
4977
+ useVoiceTurnQuality,
4978
+ useVoiceTurnLatency,
4979
+ useVoiceTraceTimeline,
1337
4980
  useVoiceStream,
4981
+ useVoiceRoutingStatus,
1338
4982
  useVoiceProviderStatus,
1339
- useVoiceController
4983
+ useVoiceProviderSimulationControls,
4984
+ useVoiceProviderContracts,
4985
+ useVoiceProviderCapabilities,
4986
+ useVoiceOpsStatus,
4987
+ useVoiceOpsActionCenter,
4988
+ useVoiceDeliveryRuntime,
4989
+ useVoiceController,
4990
+ useVoiceCampaignDialerProof,
4991
+ VoiceTurnQuality,
4992
+ VoiceTurnLatency,
4993
+ VoiceTraceTimeline,
4994
+ VoiceRoutingStatus,
4995
+ VoiceProviderStatus,
4996
+ VoiceProviderSimulationControls,
4997
+ VoiceProviderContracts,
4998
+ VoiceProviderCapabilities,
4999
+ VoiceOpsStatus,
5000
+ VoiceOpsActionCenter,
5001
+ VoiceDeliveryRuntime
1340
5002
  };