@absolutejs/voice 0.0.22-beta.29 → 0.0.22-beta.290

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 (228) hide show
  1. package/README.md +3234 -228
  2. package/dist/agent.d.ts +62 -0
  3. package/dist/agentSquadContract.d.ts +98 -0
  4. package/dist/angular/index.d.ts +15 -0
  5. package/dist/angular/index.js +3401 -1153
  6. package/dist/angular/voice-agent-squad-status.service.d.ts +12 -0
  7. package/dist/angular/voice-campaign-dialer-proof.service.d.ts +14 -0
  8. package/dist/angular/voice-controller.service.d.ts +1 -0
  9. package/dist/angular/voice-delivery-runtime.component.d.ts +17 -0
  10. package/dist/angular/voice-delivery-runtime.service.d.ts +16 -0
  11. package/dist/angular/voice-live-ops.service.d.ts +11 -0
  12. package/dist/angular/voice-ops-action-center.service.d.ts +13 -0
  13. package/dist/angular/voice-ops-status.component.d.ts +15 -0
  14. package/dist/angular/voice-ops-status.service.d.ts +12 -0
  15. package/dist/angular/voice-platform-coverage.service.d.ts +12 -0
  16. package/dist/angular/voice-proof-trends.service.d.ts +12 -0
  17. package/dist/angular/voice-provider-capabilities.service.d.ts +12 -0
  18. package/dist/angular/voice-provider-contracts.service.d.ts +12 -0
  19. package/dist/angular/voice-routing-status.service.d.ts +11 -0
  20. package/dist/angular/voice-stream.service.d.ts +1 -0
  21. package/dist/angular/voice-trace-timeline.service.d.ts +12 -0
  22. package/dist/angular/voice-turn-latency.service.d.ts +13 -0
  23. package/dist/angular/voice-turn-quality.service.d.ts +12 -0
  24. package/dist/angular/voice-workflow-status.service.d.ts +12 -0
  25. package/dist/audit.d.ts +128 -0
  26. package/dist/auditDeliveryRoutes.d.ts +85 -0
  27. package/dist/auditExport.d.ts +34 -0
  28. package/dist/auditRoutes.d.ts +66 -0
  29. package/dist/auditSinks.d.ts +151 -0
  30. package/dist/bargeInRoutes.d.ts +56 -0
  31. package/dist/campaign.d.ts +768 -0
  32. package/dist/campaignDialers.d.ts +111 -0
  33. package/dist/client/actions.d.ts +83 -0
  34. package/dist/client/agentSquadStatus.d.ts +37 -0
  35. package/dist/client/agentSquadStatusWidget.d.ts +24 -0
  36. package/dist/client/bargeInMonitor.d.ts +7 -0
  37. package/dist/client/campaignDialerProof.d.ts +23 -0
  38. package/dist/client/deliveryRuntime.d.ts +34 -0
  39. package/dist/client/deliveryRuntimeWidget.d.ts +37 -0
  40. package/dist/client/duplex.d.ts +1 -1
  41. package/dist/client/htmxBootstrap.js +703 -13
  42. package/dist/client/index.d.ts +66 -0
  43. package/dist/client/index.js +5073 -19
  44. package/dist/client/liveOps.d.ts +22 -0
  45. package/dist/client/liveOpsWidget.d.ts +23 -0
  46. package/dist/client/liveTurnLatency.d.ts +41 -0
  47. package/dist/client/opsActionCenter.d.ts +54 -0
  48. package/dist/client/opsActionCenterWidget.d.ts +29 -0
  49. package/dist/client/opsActionHistory.d.ts +19 -0
  50. package/dist/client/opsActionHistoryWidget.d.ts +11 -0
  51. package/dist/client/opsStatus.d.ts +19 -0
  52. package/dist/client/opsStatusWidget.d.ts +40 -0
  53. package/dist/client/platformCoverage.d.ts +19 -0
  54. package/dist/client/platformCoverageWidget.d.ts +37 -0
  55. package/dist/client/proofTrends.d.ts +19 -0
  56. package/dist/client/proofTrendsWidget.d.ts +37 -0
  57. package/dist/client/providerCapabilities.d.ts +19 -0
  58. package/dist/client/providerCapabilitiesWidget.d.ts +32 -0
  59. package/dist/client/providerContracts.d.ts +19 -0
  60. package/dist/client/providerContractsWidget.d.ts +37 -0
  61. package/dist/client/providerSimulationControls.d.ts +33 -0
  62. package/dist/client/providerSimulationControlsWidget.d.ts +20 -0
  63. package/dist/client/providerStatusWidget.d.ts +32 -0
  64. package/dist/client/routingStatus.d.ts +19 -0
  65. package/dist/client/routingStatusWidget.d.ts +28 -0
  66. package/dist/client/traceTimeline.d.ts +19 -0
  67. package/dist/client/traceTimelineWidget.d.ts +36 -0
  68. package/dist/client/turnLatency.d.ts +22 -0
  69. package/dist/client/turnLatencyWidget.d.ts +33 -0
  70. package/dist/client/turnQuality.d.ts +19 -0
  71. package/dist/client/turnQualityWidget.d.ts +32 -0
  72. package/dist/client/workflowStatus.d.ts +19 -0
  73. package/dist/dataControl.d.ts +180 -0
  74. package/dist/deliveryRuntime.d.ts +158 -0
  75. package/dist/deliverySinkRoutes.d.ts +117 -0
  76. package/dist/demoReadyRoutes.d.ts +98 -0
  77. package/dist/diagnosticsRoutes.d.ts +44 -0
  78. package/dist/evalRoutes.d.ts +219 -0
  79. package/dist/fileStore.d.ts +14 -2
  80. package/dist/guardrails.d.ts +128 -0
  81. package/dist/handoff.d.ts +15 -1
  82. package/dist/handoffHealth.d.ts +94 -0
  83. package/dist/incidentBundle.d.ts +116 -0
  84. package/dist/index.d.ts +138 -15
  85. package/dist/index.js +26946 -4957
  86. package/dist/latencySlo.d.ts +56 -0
  87. package/dist/liveLatency.d.ts +78 -0
  88. package/dist/liveOps.d.ts +190 -0
  89. package/dist/modelAdapters.d.ts +23 -2
  90. package/dist/observabilityExport.d.ts +481 -0
  91. package/dist/openaiRealtime.d.ts +27 -0
  92. package/dist/openaiTTS.d.ts +18 -0
  93. package/dist/operationsRecord.d.ts +210 -0
  94. package/dist/opsActionAuditRoutes.d.ts +99 -0
  95. package/dist/opsConsoleRoutes.d.ts +80 -0
  96. package/dist/opsRecovery.d.ts +137 -0
  97. package/dist/opsStatus.d.ts +76 -0
  98. package/dist/opsStatusRoutes.d.ts +33 -0
  99. package/dist/outcomeContract.d.ts +146 -0
  100. package/dist/phoneAgent.d.ts +139 -0
  101. package/dist/phoneAgentProductionSmoke.d.ts +115 -0
  102. package/dist/platformCoverage.d.ts +91 -0
  103. package/dist/postCallAnalysis.d.ts +98 -0
  104. package/dist/postgresStore.d.ts +13 -2
  105. package/dist/productionReadiness.d.ts +544 -0
  106. package/dist/proofTrends.d.ts +133 -0
  107. package/dist/providerAdapters.d.ts +48 -0
  108. package/dist/providerCapabilities.d.ts +92 -0
  109. package/dist/providerHealth.d.ts +1 -0
  110. package/dist/providerRoutingContract.d.ts +71 -0
  111. package/dist/providerSlo.d.ts +142 -0
  112. package/dist/providerStackRecommendations.d.ts +187 -0
  113. package/dist/qualityRoutes.d.ts +76 -0
  114. package/dist/queue.d.ts +61 -0
  115. package/dist/react/VoiceAgentSquadStatus.d.ts +5 -0
  116. package/dist/react/VoiceDeliveryRuntime.d.ts +7 -0
  117. package/dist/react/VoiceOpsActionCenter.d.ts +5 -0
  118. package/dist/react/VoiceOpsStatus.d.ts +6 -0
  119. package/dist/react/VoicePlatformCoverage.d.ts +6 -0
  120. package/dist/react/VoiceProofTrends.d.ts +6 -0
  121. package/dist/react/VoiceProviderCapabilities.d.ts +6 -0
  122. package/dist/react/VoiceProviderContracts.d.ts +6 -0
  123. package/dist/react/VoiceProviderSimulationControls.d.ts +5 -0
  124. package/dist/react/VoiceProviderStatus.d.ts +6 -0
  125. package/dist/react/VoiceRoutingStatus.d.ts +6 -0
  126. package/dist/react/VoiceTraceTimeline.d.ts +6 -0
  127. package/dist/react/VoiceTurnLatency.d.ts +6 -0
  128. package/dist/react/VoiceTurnQuality.d.ts +6 -0
  129. package/dist/react/index.d.ts +30 -0
  130. package/dist/react/index.js +4769 -31
  131. package/dist/react/useVoiceAgentSquadStatus.d.ts +8 -0
  132. package/dist/react/useVoiceCampaignDialerProof.d.ts +10 -0
  133. package/dist/react/useVoiceController.d.ts +1 -0
  134. package/dist/react/useVoiceDeliveryRuntime.d.ts +13 -0
  135. package/dist/react/useVoiceLiveOps.d.ts +9 -0
  136. package/dist/react/useVoiceOpsActionCenter.d.ts +11 -0
  137. package/dist/react/useVoiceOpsStatus.d.ts +8 -0
  138. package/dist/react/useVoicePlatformCoverage.d.ts +8 -0
  139. package/dist/react/useVoiceProofTrends.d.ts +8 -0
  140. package/dist/react/useVoiceProviderCapabilities.d.ts +8 -0
  141. package/dist/react/useVoiceProviderContracts.d.ts +8 -0
  142. package/dist/react/useVoiceProviderSimulationControls.d.ts +10 -0
  143. package/dist/react/useVoiceRoutingStatus.d.ts +8 -0
  144. package/dist/react/useVoiceStream.d.ts +1 -0
  145. package/dist/react/useVoiceTraceTimeline.d.ts +8 -0
  146. package/dist/react/useVoiceTurnLatency.d.ts +9 -0
  147. package/dist/react/useVoiceTurnQuality.d.ts +8 -0
  148. package/dist/react/useVoiceWorkflowStatus.d.ts +8 -0
  149. package/dist/readinessProfiles.d.ts +38 -0
  150. package/dist/reconnectContract.d.ts +88 -0
  151. package/dist/resilienceRoutes.d.ts +143 -0
  152. package/dist/sessionReplay.d.ts +12 -0
  153. package/dist/simulationSuite.d.ts +143 -0
  154. package/dist/sloCalibration.d.ts +185 -0
  155. package/dist/sqliteStore.d.ts +13 -2
  156. package/dist/svelte/createVoiceAgentSquadStatus.d.ts +9 -0
  157. package/dist/svelte/createVoiceCampaignDialerProof.d.ts +9 -0
  158. package/dist/svelte/createVoiceDeliveryRuntime.d.ts +11 -0
  159. package/dist/svelte/createVoiceLiveOps.d.ts +13 -0
  160. package/dist/svelte/createVoiceOpsActionCenter.d.ts +10 -0
  161. package/dist/svelte/createVoiceOpsStatus.d.ts +9 -0
  162. package/dist/svelte/createVoicePlatformCoverage.d.ts +7 -0
  163. package/dist/svelte/createVoiceProofTrends.d.ts +7 -0
  164. package/dist/svelte/createVoiceProviderCapabilities.d.ts +10 -0
  165. package/dist/svelte/createVoiceProviderContracts.d.ts +10 -0
  166. package/dist/svelte/createVoiceProviderSimulationControls.d.ts +11 -0
  167. package/dist/svelte/createVoiceProviderStatus.d.ts +4 -2
  168. package/dist/svelte/createVoiceRoutingStatus.d.ts +10 -0
  169. package/dist/svelte/createVoiceTraceTimeline.d.ts +10 -0
  170. package/dist/svelte/createVoiceTurnLatency.d.ts +11 -0
  171. package/dist/svelte/createVoiceTurnQuality.d.ts +10 -0
  172. package/dist/svelte/createVoiceWorkflowStatus.d.ts +8 -0
  173. package/dist/svelte/index.d.ts +16 -0
  174. package/dist/svelte/index.js +4838 -420
  175. package/dist/telephony/contract.d.ts +61 -0
  176. package/dist/telephony/matrix.d.ts +97 -0
  177. package/dist/telephony/plivo.d.ts +303 -0
  178. package/dist/telephony/security.d.ts +182 -0
  179. package/dist/telephony/telnyx.d.ts +291 -0
  180. package/dist/telephony/twilio.d.ts +135 -2
  181. package/dist/telephonyOutcome.d.ts +273 -0
  182. package/dist/testing/index.d.ts +1 -0
  183. package/dist/testing/index.js +1778 -36
  184. package/dist/testing/ioProviderSimulator.d.ts +41 -0
  185. package/dist/toolContract.d.ts +161 -0
  186. package/dist/toolRuntime.d.ts +50 -0
  187. package/dist/trace.d.ts +19 -1
  188. package/dist/traceDeliveryRoutes.d.ts +86 -0
  189. package/dist/traceTimeline.d.ts +97 -0
  190. package/dist/turnLatency.d.ts +95 -0
  191. package/dist/turnQuality.d.ts +94 -0
  192. package/dist/types.d.ts +127 -3
  193. package/dist/voiceMonitoring.d.ts +444 -0
  194. package/dist/vue/VoiceDeliveryRuntime.d.ts +30 -0
  195. package/dist/vue/VoiceOpsActionCenter.d.ts +13 -0
  196. package/dist/vue/VoiceOpsStatus.d.ts +30 -0
  197. package/dist/vue/VoicePlatformCoverage.d.ts +23 -0
  198. package/dist/vue/VoiceProofTrends.d.ts +21 -0
  199. package/dist/vue/VoiceProviderCapabilities.d.ts +51 -0
  200. package/dist/vue/VoiceProviderContracts.d.ts +21 -0
  201. package/dist/vue/VoiceProviderSimulationControls.d.ts +88 -0
  202. package/dist/vue/VoiceProviderStatus.d.ts +51 -0
  203. package/dist/vue/VoiceRoutingStatus.d.ts +51 -0
  204. package/dist/vue/VoiceTurnLatency.d.ts +69 -0
  205. package/dist/vue/VoiceTurnQuality.d.ts +51 -0
  206. package/dist/vue/index.d.ts +28 -0
  207. package/dist/vue/index.js +4550 -56
  208. package/dist/vue/useVoiceAgentSquadStatus.d.ts +9 -0
  209. package/dist/vue/useVoiceCampaignDialerProof.d.ts +11 -0
  210. package/dist/vue/useVoiceController.d.ts +2 -1
  211. package/dist/vue/useVoiceDeliveryRuntime.d.ts +13 -0
  212. package/dist/vue/useVoiceLiveOps.d.ts +9 -0
  213. package/dist/vue/useVoiceOpsActionCenter.d.ts +11 -0
  214. package/dist/vue/useVoiceOpsStatus.d.ts +9 -0
  215. package/dist/vue/useVoicePlatformCoverage.d.ts +9 -0
  216. package/dist/vue/useVoiceProofTrends.d.ts +9 -0
  217. package/dist/vue/useVoiceProviderCapabilities.d.ts +9 -0
  218. package/dist/vue/useVoiceProviderContracts.d.ts +9 -0
  219. package/dist/vue/useVoiceProviderSimulationControls.d.ts +24 -0
  220. package/dist/vue/useVoiceProviderStatus.d.ts +1 -1
  221. package/dist/vue/useVoiceRoutingStatus.d.ts +8 -0
  222. package/dist/vue/useVoiceStream.d.ts +2 -1
  223. package/dist/vue/useVoiceTraceTimeline.d.ts +9 -0
  224. package/dist/vue/useVoiceTurnLatency.d.ts +10 -0
  225. package/dist/vue/useVoiceTurnQuality.d.ts +9 -0
  226. package/dist/vue/useVoiceWorkflowStatus.d.ts +9 -0
  227. package/dist/workflowContract.d.ts +91 -0
  228. package/package.json +1 -1
package/dist/vue/index.js CHANGED
@@ -69,8 +69,4299 @@ 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/vue/VoiceOpsStatus.ts
73
+ import { defineComponent, h } from "vue";
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/client/opsStatusWidget.ts
155
+ var DEFAULT_TITLE = "Voice Ops Status";
156
+ var DEFAULT_DESCRIPTION = "Certified workflow, provider, and handoff readiness from your AbsoluteJS voice app.";
157
+ var SURFACE_LABELS = {
158
+ handoffs: "Handoffs",
159
+ providers: "Providers",
160
+ quality: "Quality",
161
+ sessions: "Sessions",
162
+ workflows: "Workflows"
163
+ };
164
+ var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
165
+ var readNumber = (value, key) => value && typeof value === "object" && (key in value) ? Number(value[key] ?? 0) : 0;
166
+ var surfaceDetail = (surface) => {
167
+ const total = readNumber(surface, "total");
168
+ const failed = readNumber(surface, "failed");
169
+ const degraded = readNumber(surface, "degraded");
170
+ const source = surface && typeof surface === "object" && "source" in surface && typeof surface.source === "string" ? ` from ${surface.source}` : "";
171
+ if (degraded > 0) {
172
+ return `${degraded} degraded of ${total}`;
173
+ }
174
+ if (failed > 0) {
175
+ return `${failed} failing of ${total}${source}`;
176
+ }
177
+ return total > 0 ? `${total} passing${source}` : `No failures${source}`;
178
+ };
179
+ var getVoiceOpsStatusLabel = (report, error) => {
180
+ if (error) {
181
+ return "Unavailable";
182
+ }
183
+ if (!report) {
184
+ return "Checking";
185
+ }
186
+ return report.status === "pass" ? "Passing" : "Needs attention";
187
+ };
188
+ var createVoiceOpsStatusViewModel = (snapshot, options = {}) => {
189
+ const report = snapshot.report;
190
+ const surfaces = Object.entries(report?.surfaces ?? {}).map(([id, surface]) => {
191
+ const failed = readNumber(surface, "failed") || readNumber(surface, "degraded");
192
+ const total = readNumber(surface, "total");
193
+ const status = surface && typeof surface === "object" && "status" in surface ? surface.status ?? "pass" : "pass";
194
+ return {
195
+ detail: surfaceDetail(surface),
196
+ failed,
197
+ id,
198
+ label: SURFACE_LABELS[id] ?? id,
199
+ status,
200
+ total
201
+ };
202
+ });
203
+ return {
204
+ description: options.description ?? DEFAULT_DESCRIPTION,
205
+ error: snapshot.error,
206
+ isLoading: snapshot.isLoading,
207
+ label: getVoiceOpsStatusLabel(report, snapshot.error),
208
+ links: options.includeLinks === false ? [] : report?.links ?? [],
209
+ passed: report?.passed ?? 0,
210
+ status: snapshot.error ? "error" : report ? report.status : snapshot.isLoading ? "loading" : "loading",
211
+ surfaces,
212
+ title: options.title ?? DEFAULT_TITLE,
213
+ total: report?.total ?? 0,
214
+ updatedAt: snapshot.updatedAt
215
+ };
216
+ };
217
+ var renderVoiceOpsStatusHTML = (snapshot, options = {}) => {
218
+ const model = createVoiceOpsStatusViewModel(snapshot, options);
219
+ const surfaces = model.surfaces.length ? model.surfaces.map((surface) => `<li class="absolute-voice-ops-status__surface absolute-voice-ops-status__surface--${escapeHtml(surface.status)}">
220
+ <span>${escapeHtml(surface.label)}</span>
221
+ <strong>${escapeHtml(surface.detail)}</strong>
222
+ </li>`).join("") : '<li class="absolute-voice-ops-status__surface"><span>Status</span><strong>Waiting for first check</strong></li>';
223
+ 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>` : "";
224
+ return `<section class="absolute-voice-ops-status absolute-voice-ops-status--${escapeHtml(model.status)}">
225
+ <header class="absolute-voice-ops-status__header">
226
+ <span class="absolute-voice-ops-status__eyebrow">${escapeHtml(model.title)}</span>
227
+ <strong class="absolute-voice-ops-status__label">${escapeHtml(model.label)}</strong>
228
+ </header>
229
+ <p class="absolute-voice-ops-status__description">${escapeHtml(model.description)}</p>
230
+ <div class="absolute-voice-ops-status__summary">
231
+ <span>${model.passed} passing</span>
232
+ <span>${Math.max(model.total - model.passed, 0)} failing</span>
233
+ <span>${model.total} checks</span>
234
+ </div>
235
+ <ul class="absolute-voice-ops-status__surfaces">${surfaces}</ul>
236
+ ${model.error ? `<p class="absolute-voice-ops-status__error">${escapeHtml(model.error)}</p>` : ""}
237
+ ${links}
238
+ </section>`;
239
+ };
240
+ 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}`;
241
+ var mountVoiceOpsStatus = (element, path = "/api/voice/ops-status", options = {}) => {
242
+ const store = createVoiceOpsStatusStore(path, options);
243
+ const render = () => {
244
+ element.innerHTML = renderVoiceOpsStatusHTML(store.getSnapshot(), options);
245
+ };
246
+ const unsubscribe = store.subscribe(render);
247
+ render();
248
+ store.refresh().catch(() => {});
249
+ return {
250
+ close: () => {
251
+ unsubscribe();
252
+ store.close();
253
+ },
254
+ refresh: store.refresh
255
+ };
256
+ };
257
+ var defineVoiceOpsStatusElement = (tagName = "absolute-voice-ops-status") => {
258
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
259
+ return;
260
+ }
261
+ customElements.define(tagName, class AbsoluteVoiceOpsStatusElement extends HTMLElement {
262
+ mounted;
263
+ connectedCallback() {
264
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
265
+ this.mounted = mountVoiceOpsStatus(this, this.getAttribute("path") ?? "/api/voice/ops-status", {
266
+ description: this.getAttribute("description") ?? undefined,
267
+ includeLinks: this.getAttribute("include-links") !== "false",
268
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
269
+ title: this.getAttribute("title") ?? undefined
270
+ });
271
+ }
272
+ disconnectedCallback() {
273
+ this.mounted?.close();
274
+ this.mounted = undefined;
275
+ }
276
+ });
277
+ };
278
+
279
+ // src/vue/useVoiceOpsStatus.ts
280
+ import { onUnmounted, ref, shallowRef } from "vue";
281
+ function useVoiceOpsStatus(path = "/api/voice/ops-status", options = {}) {
282
+ const store = createVoiceOpsStatusStore(path, options);
283
+ const error = ref(null);
284
+ const isLoading = ref(false);
285
+ const report = shallowRef(undefined);
286
+ const updatedAt = ref(undefined);
287
+ const sync = () => {
288
+ const snapshot = store.getSnapshot();
289
+ error.value = snapshot.error;
290
+ isLoading.value = snapshot.isLoading;
291
+ report.value = snapshot.report;
292
+ updatedAt.value = snapshot.updatedAt;
293
+ };
294
+ const unsubscribe = store.subscribe(sync);
295
+ sync();
296
+ if (typeof window !== "undefined") {
297
+ store.refresh().catch(() => {});
298
+ }
299
+ onUnmounted(() => {
300
+ unsubscribe();
301
+ store.close();
302
+ });
303
+ return {
304
+ error,
305
+ isLoading,
306
+ refresh: store.refresh,
307
+ report,
308
+ updatedAt
309
+ };
310
+ }
311
+
312
+ // src/vue/VoiceOpsStatus.ts
313
+ var VoiceOpsStatus = defineComponent({
314
+ name: "VoiceOpsStatus",
315
+ props: {
316
+ description: String,
317
+ includeLinks: {
318
+ default: true,
319
+ type: Boolean
320
+ },
321
+ intervalMs: Number,
322
+ path: {
323
+ default: "/api/voice/ops-status",
324
+ type: String
325
+ },
326
+ title: String
327
+ },
328
+ setup(props) {
329
+ const options = {
330
+ description: props.description,
331
+ includeLinks: props.includeLinks,
332
+ intervalMs: props.intervalMs,
333
+ title: props.title
334
+ };
335
+ const status = useVoiceOpsStatus(props.path, options);
336
+ return () => {
337
+ const model = createVoiceOpsStatusViewModel({
338
+ error: status.error.value,
339
+ isLoading: status.isLoading.value,
340
+ report: status.report.value,
341
+ updatedAt: status.updatedAt.value
342
+ }, options);
343
+ return h("section", {
344
+ class: [
345
+ "absolute-voice-ops-status",
346
+ `absolute-voice-ops-status--${model.status}`
347
+ ]
348
+ }, [
349
+ h("header", { class: "absolute-voice-ops-status__header" }, [
350
+ h("span", { class: "absolute-voice-ops-status__eyebrow" }, model.title),
351
+ h("strong", { class: "absolute-voice-ops-status__label" }, model.label)
352
+ ]),
353
+ h("p", { class: "absolute-voice-ops-status__description" }, model.description),
354
+ h("div", { class: "absolute-voice-ops-status__summary" }, [
355
+ h("span", `${model.passed} passing`),
356
+ h("span", `${Math.max(model.total - model.passed, 0)} failing`),
357
+ h("span", `${model.total} checks`)
358
+ ]),
359
+ h("ul", { class: "absolute-voice-ops-status__surfaces" }, model.surfaces.length > 0 ? model.surfaces.map((surface) => h("li", {
360
+ class: [
361
+ "absolute-voice-ops-status__surface",
362
+ `absolute-voice-ops-status__surface--${surface.status}`
363
+ ],
364
+ key: surface.id
365
+ }, [h("span", surface.label), h("strong", surface.detail)])) : [
366
+ h("li", { class: "absolute-voice-ops-status__surface" }, [
367
+ h("span", "Status"),
368
+ h("strong", "Waiting for first check")
369
+ ])
370
+ ]),
371
+ model.error ? h("p", { class: "absolute-voice-ops-status__error" }, model.error) : null,
372
+ model.links.length > 0 ? h("nav", { class: "absolute-voice-ops-status__links" }, model.links.slice(0, 4).map((link) => h("a", { href: link.href, key: link.href }, link.label))) : null
373
+ ]);
374
+ };
375
+ }
376
+ });
377
+ // src/vue/VoiceOpsActionCenter.ts
378
+ import { defineComponent as defineComponent2, h as h2 } from "vue";
379
+
380
+ // src/client/opsActionCenter.ts
381
+ var recordVoiceOpsActionResult = async (result, options = {}) => {
382
+ if (options.auditPath === false) {
383
+ return;
384
+ }
385
+ const path = options.auditPath ?? "/api/voice/ops-actions/audit";
386
+ const fetchImpl = options.fetch ?? globalThis.fetch;
387
+ const response = await fetchImpl(path, {
388
+ body: JSON.stringify(result),
389
+ headers: {
390
+ "Content-Type": "application/json"
391
+ },
392
+ method: "POST"
393
+ });
394
+ if (!response.ok) {
395
+ throw new Error(`Voice ops action audit failed: HTTP ${response.status}`);
396
+ }
397
+ };
398
+ var createVoiceOpsActionCenterActions = (options = {}) => {
399
+ const deliveryRuntimePath = options.deliveryRuntimePath ?? "/api/voice-delivery-runtime";
400
+ const actions = [];
401
+ if (options.includeProductionReadiness !== false) {
402
+ actions.push({
403
+ description: "Refresh the production readiness report.",
404
+ id: "production-readiness",
405
+ label: "Refresh readiness",
406
+ method: "GET",
407
+ path: options.productionReadinessPath ?? "/api/production-readiness"
408
+ });
409
+ }
410
+ if (options.includeDeliveryRuntime !== false) {
411
+ actions.push({
412
+ description: "Drain pending and failed audit/trace deliveries.",
413
+ id: "delivery-runtime.tick",
414
+ label: "Tick delivery workers",
415
+ method: "POST",
416
+ path: `${deliveryRuntimePath.replace(/\/$/, "")}/tick`
417
+ }, {
418
+ description: "Move reviewed dead letters back to live delivery queues.",
419
+ id: "delivery-runtime.requeue-dead-letters",
420
+ label: "Requeue dead letters",
421
+ method: "POST",
422
+ path: `${deliveryRuntimePath.replace(/\/$/, "")}/requeue-dead-letters`
423
+ });
424
+ }
425
+ if (options.includeTurnLatencyProof !== false) {
426
+ actions.push({
427
+ description: "Run the synthetic turn latency proof.",
428
+ id: "turn-latency.proof",
429
+ label: "Run latency proof",
430
+ method: "POST",
431
+ path: options.turnLatencyProofPath ?? "/api/turn-latency/proof"
432
+ });
433
+ }
434
+ if (options.includeProviderSimulation !== false) {
435
+ const pathPrefix = options.providerSimulationPathPrefix ?? "/api/stt-simulate";
436
+ for (const provider of options.providers ?? []) {
437
+ actions.push({
438
+ description: `Simulate ${provider} provider failure.`,
439
+ id: `provider.${provider}.failure`,
440
+ label: `Simulate ${provider} failure`,
441
+ method: "POST",
442
+ path: `${pathPrefix}/failure?provider=${encodeURIComponent(provider)}`
443
+ }, {
444
+ description: `Mark ${provider} provider recovered.`,
445
+ id: `provider.${provider}.recovery`,
446
+ label: `Recover ${provider}`,
447
+ method: "POST",
448
+ path: `${pathPrefix}/recovery?provider=${encodeURIComponent(provider)}`
449
+ });
450
+ }
451
+ }
452
+ return actions;
453
+ };
454
+ var runVoiceOpsAction = async (action, options = {}) => {
455
+ const fetchImpl = options.fetch ?? globalThis.fetch;
456
+ const response = await fetchImpl(action.path, {
457
+ method: action.method ?? "POST"
458
+ });
459
+ const body = await response.json().catch(() => null);
460
+ if (!response.ok) {
461
+ const message = body && typeof body === "object" && "error" in body ? String(body.error) : `Voice ops action "${action.id}" failed: HTTP ${response.status}`;
462
+ throw new Error(message);
463
+ }
464
+ return {
465
+ actionId: action.id,
466
+ body,
467
+ ok: response.ok,
468
+ ranAt: Date.now(),
469
+ status: response.status
470
+ };
471
+ };
472
+ var createVoiceOpsActionCenterStore = (options = {}) => {
473
+ const listeners = new Set;
474
+ let closed = false;
475
+ let timer;
476
+ let snapshot = {
477
+ actions: options.actions ?? createVoiceOpsActionCenterActions(),
478
+ error: null,
479
+ isRunning: false
480
+ };
481
+ const emit = () => {
482
+ for (const listener of listeners) {
483
+ listener();
484
+ }
485
+ };
486
+ const setActions = (actions) => {
487
+ snapshot = { ...snapshot, actions, updatedAt: Date.now() };
488
+ emit();
489
+ };
490
+ const run = async (actionId) => {
491
+ if (closed) {
492
+ return snapshot.lastResult;
493
+ }
494
+ const action = snapshot.actions.find((item) => item.id === actionId);
495
+ if (!action) {
496
+ throw new Error(`Voice ops action "${actionId}" is not configured.`);
497
+ }
498
+ if (action.disabled) {
499
+ throw new Error(`Voice ops action "${actionId}" is disabled.`);
500
+ }
501
+ snapshot = {
502
+ ...snapshot,
503
+ error: null,
504
+ isRunning: true,
505
+ runningActionId: action.id
506
+ };
507
+ emit();
508
+ try {
509
+ const result = await runVoiceOpsAction(action, options);
510
+ await options.onActionResult?.(result);
511
+ await recordVoiceOpsActionResult(result, options);
512
+ snapshot = {
513
+ ...snapshot,
514
+ error: null,
515
+ isRunning: false,
516
+ lastResult: result,
517
+ runningActionId: undefined,
518
+ updatedAt: Date.now()
519
+ };
520
+ emit();
521
+ return result;
522
+ } catch (error) {
523
+ const result = {
524
+ actionId: action.id,
525
+ body: null,
526
+ error: error instanceof Error ? error.message : String(error),
527
+ ok: false,
528
+ ranAt: Date.now(),
529
+ status: 0
530
+ };
531
+ await options.onActionResult?.(result);
532
+ await recordVoiceOpsActionResult(result, options).catch(() => {});
533
+ snapshot = {
534
+ ...snapshot,
535
+ error: error instanceof Error ? error.message : String(error),
536
+ isRunning: false,
537
+ runningActionId: undefined
538
+ };
539
+ emit();
540
+ throw error;
541
+ }
542
+ };
543
+ const close = () => {
544
+ closed = true;
545
+ if (timer) {
546
+ clearInterval(timer);
547
+ timer = undefined;
548
+ }
549
+ listeners.clear();
550
+ };
551
+ if (options.intervalMs && options.intervalMs > 0) {
552
+ timer = setInterval(() => {
553
+ emit();
554
+ }, options.intervalMs);
555
+ }
556
+ return {
557
+ close,
558
+ getServerSnapshot: () => snapshot,
559
+ getSnapshot: () => snapshot,
560
+ run,
561
+ setActions,
562
+ subscribe: (listener) => {
563
+ listeners.add(listener);
564
+ return () => {
565
+ listeners.delete(listener);
566
+ };
567
+ }
568
+ };
569
+ };
570
+
571
+ // src/client/opsActionCenterWidget.ts
572
+ var DEFAULT_TITLE2 = "Voice Ops Action Center";
573
+ var DEFAULT_DESCRIPTION2 = "Run production voice proofs and operator actions from one primitive panel.";
574
+ var escapeHtml2 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
575
+ var createVoiceOpsActionCenterViewModel = (snapshot, options = {}) => {
576
+ const status = snapshot.error ? "error" : snapshot.isRunning ? "running" : snapshot.lastResult ? "completed" : "ready";
577
+ return {
578
+ actions: snapshot.actions.map((action) => ({
579
+ description: action.description ?? "",
580
+ disabled: Boolean(action.disabled || snapshot.isRunning),
581
+ id: action.id,
582
+ isRunning: snapshot.runningActionId === action.id,
583
+ label: action.label
584
+ })),
585
+ description: options.description ?? DEFAULT_DESCRIPTION2,
586
+ error: snapshot.error,
587
+ isRunning: snapshot.isRunning,
588
+ label: status === "error" ? "Needs attention" : status === "running" ? "Running" : status === "completed" ? "Action completed" : "Ready",
589
+ lastResultLabel: snapshot.lastResult ? `${snapshot.lastResult.actionId} returned HTTP ${snapshot.lastResult.status}` : "No action has run yet.",
590
+ status,
591
+ title: options.title ?? DEFAULT_TITLE2
592
+ };
593
+ };
594
+ var renderVoiceOpsActionCenterHTML = (snapshot, options = {}) => {
595
+ const model = createVoiceOpsActionCenterViewModel(snapshot, options);
596
+ const actions = model.actions.map((action) => `<button type="button" data-absolute-voice-ops-action="${escapeHtml2(action.id)}"${action.disabled ? " disabled" : ""}>
597
+ ${escapeHtml2(action.isRunning ? "Working..." : action.label)}
598
+ </button>`).join("");
599
+ return `<section class="absolute-voice-ops-action-center absolute-voice-ops-action-center--${escapeHtml2(model.status)}">
600
+ <header class="absolute-voice-ops-action-center__header">
601
+ <span class="absolute-voice-ops-action-center__eyebrow">${escapeHtml2(model.title)}</span>
602
+ <strong class="absolute-voice-ops-action-center__label">${escapeHtml2(model.label)}</strong>
603
+ </header>
604
+ <p class="absolute-voice-ops-action-center__description">${escapeHtml2(model.description)}</p>
605
+ <div class="absolute-voice-ops-action-center__actions">${actions}</div>
606
+ <p class="absolute-voice-ops-action-center__result">${escapeHtml2(model.lastResultLabel)}</p>
607
+ ${model.error ? `<p class="absolute-voice-ops-action-center__error">${escapeHtml2(model.error)}</p>` : ""}
608
+ </section>`;
609
+ };
610
+ 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}`;
611
+ var mountVoiceOpsActionCenter = (element, options = {}) => {
612
+ const store = createVoiceOpsActionCenterStore(options);
613
+ const render = () => {
614
+ element.innerHTML = renderVoiceOpsActionCenterHTML(store.getSnapshot(), options);
615
+ };
616
+ const unsubscribe = store.subscribe(render);
617
+ const handleClick = (event) => {
618
+ const target = event.target;
619
+ if (!(target instanceof Element)) {
620
+ return;
621
+ }
622
+ const action = target.closest("[data-absolute-voice-ops-action]");
623
+ const actionId = action?.getAttribute("data-absolute-voice-ops-action");
624
+ if (actionId) {
625
+ store.run(actionId).catch(() => {});
626
+ }
627
+ };
628
+ element.addEventListener?.("click", handleClick);
629
+ render();
630
+ return {
631
+ close: () => {
632
+ element.removeEventListener?.("click", handleClick);
633
+ unsubscribe();
634
+ store.close();
635
+ },
636
+ run: store.run
637
+ };
638
+ };
639
+ var defineVoiceOpsActionCenterElement = (tagName = "absolute-voice-ops-action-center", options = {}) => {
640
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
641
+ return;
642
+ }
643
+ customElements.define(tagName, class AbsoluteVoiceOpsActionCenterElement extends HTMLElement {
644
+ mounted;
645
+ connectedCallback() {
646
+ this.mounted = mountVoiceOpsActionCenter(this, {
647
+ ...options,
648
+ description: this.getAttribute("description") ?? options.description,
649
+ title: this.getAttribute("title") ?? options.title
650
+ });
651
+ }
652
+ disconnectedCallback() {
653
+ this.mounted?.close();
654
+ this.mounted = undefined;
655
+ }
656
+ });
657
+ };
658
+
659
+ // src/vue/useVoiceOpsActionCenter.ts
660
+ import { onUnmounted as onUnmounted2, ref as ref2, shallowRef as shallowRef2 } from "vue";
661
+ function useVoiceOpsActionCenter(options = {}) {
662
+ const store = createVoiceOpsActionCenterStore(options);
663
+ const actions = shallowRef2([]);
664
+ const error = ref2(null);
665
+ const isRunning = ref2(false);
666
+ const lastResult = shallowRef2(undefined);
667
+ const runningActionId = ref2(undefined);
668
+ const updatedAt = ref2(undefined);
669
+ const sync = () => {
670
+ const snapshot = store.getSnapshot();
671
+ actions.value = snapshot.actions;
672
+ error.value = snapshot.error;
673
+ isRunning.value = snapshot.isRunning;
674
+ lastResult.value = snapshot.lastResult;
675
+ runningActionId.value = snapshot.runningActionId;
676
+ updatedAt.value = snapshot.updatedAt;
677
+ };
678
+ const unsubscribe = store.subscribe(sync);
679
+ sync();
680
+ onUnmounted2(() => {
681
+ unsubscribe();
682
+ store.close();
683
+ });
684
+ return {
685
+ actions,
686
+ error,
687
+ isRunning,
688
+ lastResult,
689
+ run: store.run,
690
+ runningActionId,
691
+ setActions: store.setActions,
692
+ updatedAt
693
+ };
694
+ }
695
+
696
+ // src/vue/VoiceOpsActionCenter.ts
697
+ var VoiceOpsActionCenter = defineComponent2({
698
+ name: "VoiceOpsActionCenter",
699
+ props: {
700
+ actions: Array,
701
+ description: String,
702
+ title: String
703
+ },
704
+ setup(props) {
705
+ const options = {
706
+ actions: props.actions,
707
+ description: props.description,
708
+ title: props.title
709
+ };
710
+ const center = useVoiceOpsActionCenter(options);
711
+ return () => {
712
+ const model = createVoiceOpsActionCenterViewModel({
713
+ actions: center.actions.value,
714
+ error: center.error.value,
715
+ isRunning: center.isRunning.value,
716
+ lastResult: center.lastResult.value,
717
+ runningActionId: center.runningActionId.value,
718
+ updatedAt: center.updatedAt.value
719
+ }, options);
720
+ return h2("section", {
721
+ class: [
722
+ "absolute-voice-ops-action-center",
723
+ `absolute-voice-ops-action-center--${model.status}`
724
+ ]
725
+ }, [
726
+ h2("header", { class: "absolute-voice-ops-action-center__header" }, [
727
+ h2("span", { class: "absolute-voice-ops-action-center__eyebrow" }, model.title),
728
+ h2("strong", { class: "absolute-voice-ops-action-center__label" }, model.label)
729
+ ]),
730
+ h2("p", { class: "absolute-voice-ops-action-center__description" }, model.description),
731
+ h2("div", { class: "absolute-voice-ops-action-center__actions" }, model.actions.map((action) => h2("button", {
732
+ disabled: action.disabled,
733
+ key: action.id,
734
+ onClick: () => {
735
+ center.run(action.id).catch(() => {});
736
+ },
737
+ type: "button"
738
+ }, action.isRunning ? "Working..." : action.label))),
739
+ h2("p", { class: "absolute-voice-ops-action-center__result" }, model.lastResultLabel),
740
+ model.error ? h2("p", { class: "absolute-voice-ops-action-center__error" }, model.error) : null
741
+ ]);
742
+ };
743
+ }
744
+ });
745
+ // src/vue/VoiceDeliveryRuntime.ts
746
+ import { defineComponent as defineComponent3, h as h3 } from "vue";
747
+
748
+ // src/client/deliveryRuntime.ts
749
+ var getDefaultActionPath = (path, action, options) => {
750
+ if (action === "tick") {
751
+ return options.tickPath ?? `${path.replace(/\/$/, "")}/tick`;
752
+ }
753
+ return options.requeueDeadLettersPath ?? `${path.replace(/\/$/, "")}/requeue-dead-letters`;
754
+ };
755
+ var fetchVoiceDeliveryRuntime = async (path = "/api/voice-delivery-runtime", options = {}) => {
756
+ const fetchImpl = options.fetch ?? globalThis.fetch;
757
+ const response = await fetchImpl(path);
758
+ if (!response.ok) {
759
+ throw new Error(`Voice delivery runtime failed: HTTP ${response.status}`);
760
+ }
761
+ return await response.json();
762
+ };
763
+ var runVoiceDeliveryRuntimeAction = async (action, path = "/api/voice-delivery-runtime", options = {}) => {
764
+ const fetchImpl = options.fetch ?? globalThis.fetch;
765
+ const response = await fetchImpl(getDefaultActionPath(path, action, options), {
766
+ method: "POST"
767
+ });
768
+ if (!response.ok) {
769
+ throw new Error(`Voice delivery runtime ${action} failed: HTTP ${response.status}`);
770
+ }
771
+ const body = await response.json();
772
+ return {
773
+ action,
774
+ result: body.result,
775
+ summary: body.summary,
776
+ updatedAt: Date.now()
777
+ };
778
+ };
779
+ var createVoiceDeliveryRuntimeStore = (path = "/api/voice-delivery-runtime", options = {}) => {
780
+ const listeners = new Set;
781
+ let closed = false;
782
+ let timer;
783
+ let snapshot = {
784
+ actionError: null,
785
+ actionStatus: "idle",
786
+ error: null,
787
+ isLoading: false
788
+ };
789
+ const emit = () => {
790
+ for (const listener of listeners) {
791
+ listener();
792
+ }
793
+ };
794
+ const refresh = async () => {
795
+ if (closed) {
796
+ return snapshot.report;
797
+ }
798
+ snapshot = {
799
+ ...snapshot,
800
+ error: null,
801
+ isLoading: true
802
+ };
803
+ emit();
804
+ try {
805
+ const report = await fetchVoiceDeliveryRuntime(path, options);
806
+ snapshot = {
807
+ ...snapshot,
808
+ error: null,
809
+ isLoading: false,
810
+ report,
811
+ updatedAt: Date.now()
812
+ };
813
+ emit();
814
+ return report;
815
+ } catch (error) {
816
+ snapshot = {
817
+ ...snapshot,
818
+ error: error instanceof Error ? error.message : String(error),
819
+ isLoading: false
820
+ };
821
+ emit();
822
+ throw error;
823
+ }
824
+ };
825
+ const runAction = async (action) => {
826
+ if (closed) {
827
+ return snapshot.lastAction;
828
+ }
829
+ snapshot = {
830
+ ...snapshot,
831
+ actionError: null,
832
+ actionStatus: "running"
833
+ };
834
+ emit();
835
+ try {
836
+ const result = await runVoiceDeliveryRuntimeAction(action, path, options);
837
+ snapshot = {
838
+ ...snapshot,
839
+ actionError: null,
840
+ actionStatus: "completed",
841
+ lastAction: result
842
+ };
843
+ emit();
844
+ await refresh();
845
+ return result;
846
+ } catch (error) {
847
+ snapshot = {
848
+ ...snapshot,
849
+ actionError: error instanceof Error ? error.message : String(error),
850
+ actionStatus: "failed"
851
+ };
852
+ emit();
853
+ throw error;
854
+ }
855
+ };
856
+ const close = () => {
857
+ closed = true;
858
+ if (timer) {
859
+ clearInterval(timer);
860
+ timer = undefined;
861
+ }
862
+ listeners.clear();
863
+ };
864
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
865
+ timer = setInterval(() => {
866
+ refresh().catch(() => {});
867
+ }, options.intervalMs);
868
+ }
869
+ return {
870
+ close,
871
+ getServerSnapshot: () => snapshot,
872
+ getSnapshot: () => snapshot,
873
+ requeueDeadLetters: () => runAction("requeue-dead-letters"),
874
+ refresh,
875
+ tick: () => runAction("tick"),
876
+ subscribe: (listener) => {
877
+ listeners.add(listener);
878
+ return () => {
879
+ listeners.delete(listener);
880
+ };
881
+ }
882
+ };
883
+ };
884
+
885
+ // src/client/deliveryRuntimeWidget.ts
886
+ var DEFAULT_TITLE3 = "Voice Delivery Runtime";
887
+ var DEFAULT_DESCRIPTION3 = "Audit and trace delivery worker health from your AbsoluteJS voice app.";
888
+ var escapeHtml3 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
889
+ var createSurface = (id, summary) => {
890
+ if (!summary) {
891
+ return {
892
+ deadLettered: 0,
893
+ detail: "Worker disabled",
894
+ failed: 0,
895
+ id,
896
+ label: id === "audit" ? "Audit delivery" : "Trace delivery",
897
+ pending: 0,
898
+ status: "disabled",
899
+ total: 0
900
+ };
901
+ }
902
+ const blocked = summary.failed + summary.deadLettered;
903
+ return {
904
+ deadLettered: summary.deadLettered,
905
+ detail: `${summary.delivered}/${summary.total} delivered, ${summary.pending} pending`,
906
+ failed: summary.failed,
907
+ id,
908
+ label: id === "audit" ? "Audit delivery" : "Trace delivery",
909
+ pending: summary.pending,
910
+ status: blocked > 0 ? "warn" : "pass",
911
+ total: summary.total
912
+ };
913
+ };
914
+ var createVoiceDeliveryRuntimeViewModel = (snapshot, options = {}) => {
915
+ const report = snapshot.report;
916
+ const surfaces = [
917
+ createSurface("audit", report?.summary.audit),
918
+ createSurface("trace", report?.summary.trace)
919
+ ];
920
+ const hasWarnings = surfaces.some((surface) => surface.status === "warn");
921
+ return {
922
+ description: options.description ?? DEFAULT_DESCRIPTION3,
923
+ error: snapshot.error,
924
+ actionError: snapshot.actionError,
925
+ actionStatus: snapshot.actionStatus,
926
+ isLoading: snapshot.isLoading,
927
+ isRunning: Boolean(report?.isRunning),
928
+ label: snapshot.error ? "Unavailable" : report ? report.isRunning ? "Running" : "Stopped" : "Checking",
929
+ status: snapshot.error ? "error" : report ? hasWarnings ? "warn" : "pass" : "loading",
930
+ surfaces,
931
+ title: options.title ?? DEFAULT_TITLE3,
932
+ updatedAt: snapshot.updatedAt
933
+ };
934
+ };
935
+ var renderVoiceDeliveryRuntimeHTML = (snapshot, options = {}) => {
936
+ const model = createVoiceDeliveryRuntimeViewModel(snapshot, options);
937
+ const surfaces = model.surfaces.map((surface) => `<li class="absolute-voice-delivery-runtime__surface absolute-voice-delivery-runtime__surface--${escapeHtml3(surface.status)}">
938
+ <span>${escapeHtml3(surface.label)}</span>
939
+ <strong>${escapeHtml3(surface.detail)}</strong>
940
+ <small>${String(surface.failed)} failed &middot; ${String(surface.deadLettered)} dead-lettered</small>
941
+ </li>`).join("");
942
+ const actions = options.includeActions === false ? "" : `<div class="absolute-voice-delivery-runtime__actions">
943
+ <button type="button" data-absolute-voice-delivery-runtime-action="tick">${model.actionStatus === "running" ? "Working..." : "Tick workers"}</button>
944
+ <button type="button" data-absolute-voice-delivery-runtime-action="requeue-dead-letters"${model.surfaces.some((surface) => surface.deadLettered > 0) ? "" : " disabled"}>Requeue dead letters</button>
945
+ </div>`;
946
+ const actionError = model.actionError ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml3(model.actionError)}</p>` : "";
947
+ return `<section class="absolute-voice-delivery-runtime absolute-voice-delivery-runtime--${escapeHtml3(model.status)}">
948
+ <header class="absolute-voice-delivery-runtime__header">
949
+ <span class="absolute-voice-delivery-runtime__eyebrow">${escapeHtml3(model.title)}</span>
950
+ <strong class="absolute-voice-delivery-runtime__label">${escapeHtml3(model.label)}</strong>
951
+ </header>
952
+ <p class="absolute-voice-delivery-runtime__description">${escapeHtml3(model.description)}</p>
953
+ <ul class="absolute-voice-delivery-runtime__surfaces">${surfaces}</ul>
954
+ ${actions}
955
+ ${actionError}
956
+ ${model.error ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml3(model.error)}</p>` : ""}
957
+ </section>`;
958
+ };
959
+ 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}`;
960
+ var mountVoiceDeliveryRuntime = (element, path = "/api/voice-delivery-runtime", options = {}) => {
961
+ const store = createVoiceDeliveryRuntimeStore(path, options);
962
+ const render = () => {
963
+ element.innerHTML = renderVoiceDeliveryRuntimeHTML(store.getSnapshot(), options);
964
+ };
965
+ const unsubscribe = store.subscribe(render);
966
+ const handleClick = (event) => {
967
+ const target = event.target;
968
+ if (!(target instanceof Element)) {
969
+ return;
970
+ }
971
+ const action = target.closest("[data-absolute-voice-delivery-runtime-action]");
972
+ const actionName = action?.getAttribute("data-absolute-voice-delivery-runtime-action");
973
+ if (actionName === "tick") {
974
+ store.tick().catch(() => {});
975
+ }
976
+ if (actionName === "requeue-dead-letters") {
977
+ store.requeueDeadLetters().catch(() => {});
978
+ }
979
+ };
980
+ element.addEventListener?.("click", handleClick);
981
+ render();
982
+ store.refresh().catch(() => {});
983
+ return {
984
+ close: () => {
985
+ element.removeEventListener?.("click", handleClick);
986
+ unsubscribe();
987
+ store.close();
988
+ },
989
+ refresh: store.refresh
990
+ };
991
+ };
992
+ var defineVoiceDeliveryRuntimeElement = (tagName = "absolute-voice-delivery-runtime") => {
993
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
994
+ return;
995
+ }
996
+ customElements.define(tagName, class AbsoluteVoiceDeliveryRuntimeElement extends HTMLElement {
997
+ mounted;
998
+ connectedCallback() {
999
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
1000
+ this.mounted = mountVoiceDeliveryRuntime(this, this.getAttribute("path") ?? "/api/voice-delivery-runtime", {
1001
+ description: this.getAttribute("description") ?? undefined,
1002
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
1003
+ title: this.getAttribute("title") ?? undefined
1004
+ });
1005
+ }
1006
+ disconnectedCallback() {
1007
+ this.mounted?.close();
1008
+ this.mounted = undefined;
1009
+ }
1010
+ });
1011
+ };
1012
+
1013
+ // src/vue/useVoiceDeliveryRuntime.ts
1014
+ import { onUnmounted as onUnmounted3, ref as ref3, shallowRef as shallowRef3 } from "vue";
1015
+ function useVoiceDeliveryRuntime(path = "/api/voice-delivery-runtime", options = {}) {
1016
+ const store = createVoiceDeliveryRuntimeStore(path, options);
1017
+ const actionError = ref3(null);
1018
+ const actionStatus = ref3("idle");
1019
+ const error = ref3(null);
1020
+ const isLoading = ref3(false);
1021
+ const report = shallowRef3(undefined);
1022
+ const updatedAt = ref3(undefined);
1023
+ const sync = () => {
1024
+ const snapshot = store.getSnapshot();
1025
+ actionError.value = snapshot.actionError;
1026
+ actionStatus.value = snapshot.actionStatus;
1027
+ error.value = snapshot.error;
1028
+ isLoading.value = snapshot.isLoading;
1029
+ report.value = snapshot.report;
1030
+ updatedAt.value = snapshot.updatedAt;
1031
+ };
1032
+ const unsubscribe = store.subscribe(sync);
1033
+ sync();
1034
+ if (typeof window !== "undefined") {
1035
+ store.refresh().catch(() => {});
1036
+ }
1037
+ onUnmounted3(() => {
1038
+ unsubscribe();
1039
+ store.close();
1040
+ });
1041
+ return {
1042
+ actionError,
1043
+ actionStatus,
1044
+ error,
1045
+ isLoading,
1046
+ requeueDeadLetters: store.requeueDeadLetters,
1047
+ refresh: store.refresh,
1048
+ report,
1049
+ tick: store.tick,
1050
+ updatedAt
1051
+ };
1052
+ }
1053
+
1054
+ // src/vue/VoiceDeliveryRuntime.ts
1055
+ var VoiceDeliveryRuntime = defineComponent3({
1056
+ name: "VoiceDeliveryRuntime",
1057
+ props: {
1058
+ description: String,
1059
+ includeActions: {
1060
+ default: true,
1061
+ type: Boolean
1062
+ },
1063
+ intervalMs: Number,
1064
+ path: {
1065
+ default: "/api/voice-delivery-runtime",
1066
+ type: String
1067
+ },
1068
+ title: String
1069
+ },
1070
+ setup(props) {
1071
+ const options = {
1072
+ description: props.description,
1073
+ intervalMs: props.intervalMs,
1074
+ title: props.title
1075
+ };
1076
+ const runtime = useVoiceDeliveryRuntime(props.path, options);
1077
+ return () => {
1078
+ const model = createVoiceDeliveryRuntimeViewModel({
1079
+ error: runtime.error.value,
1080
+ actionError: runtime.actionError.value,
1081
+ actionStatus: runtime.actionStatus.value,
1082
+ isLoading: runtime.isLoading.value,
1083
+ report: runtime.report.value,
1084
+ updatedAt: runtime.updatedAt.value
1085
+ }, options);
1086
+ const hasDeadLetters = model.surfaces.some((surface) => surface.deadLettered > 0);
1087
+ return h3("section", {
1088
+ class: [
1089
+ "absolute-voice-delivery-runtime",
1090
+ `absolute-voice-delivery-runtime--${model.status}`
1091
+ ]
1092
+ }, [
1093
+ h3("header", { class: "absolute-voice-delivery-runtime__header" }, [
1094
+ h3("span", { class: "absolute-voice-delivery-runtime__eyebrow" }, model.title),
1095
+ h3("strong", { class: "absolute-voice-delivery-runtime__label" }, model.label)
1096
+ ]),
1097
+ h3("p", { class: "absolute-voice-delivery-runtime__description" }, model.description),
1098
+ h3("ul", { class: "absolute-voice-delivery-runtime__surfaces" }, model.surfaces.map((surface) => h3("li", {
1099
+ class: [
1100
+ "absolute-voice-delivery-runtime__surface",
1101
+ `absolute-voice-delivery-runtime__surface--${surface.status}`
1102
+ ],
1103
+ key: surface.id
1104
+ }, [
1105
+ h3("span", surface.label),
1106
+ h3("strong", surface.detail),
1107
+ h3("small", `${surface.failed} failed / ${surface.deadLettered} dead-lettered`)
1108
+ ]))),
1109
+ props.includeActions ? h3("div", { class: "absolute-voice-delivery-runtime__actions" }, [
1110
+ h3("button", {
1111
+ disabled: model.actionStatus === "running",
1112
+ onClick: () => {
1113
+ runtime.tick().catch(() => {});
1114
+ },
1115
+ type: "button"
1116
+ }, model.actionStatus === "running" ? "Working..." : "Tick workers"),
1117
+ h3("button", {
1118
+ disabled: model.actionStatus === "running" || !hasDeadLetters,
1119
+ onClick: () => {
1120
+ runtime.requeueDeadLetters().catch(() => {});
1121
+ },
1122
+ type: "button"
1123
+ }, "Requeue dead letters")
1124
+ ]) : null,
1125
+ model.actionError ? h3("p", { class: "absolute-voice-delivery-runtime__error" }, model.actionError) : null,
1126
+ model.error ? h3("p", { class: "absolute-voice-delivery-runtime__error" }, model.error) : null
1127
+ ]);
1128
+ };
1129
+ }
1130
+ });
1131
+ // src/vue/VoicePlatformCoverage.ts
1132
+ import { defineComponent as defineComponent4, h as h4 } from "vue";
1133
+
1134
+ // src/vue/useVoicePlatformCoverage.ts
1135
+ import { onUnmounted as onUnmounted4, ref as ref4, shallowRef as shallowRef4 } from "vue";
1136
+
1137
+ // src/client/platformCoverage.ts
1138
+ var fetchVoicePlatformCoverage = async (path = "/api/voice/platform-coverage", options = {}) => {
1139
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1140
+ const response = await fetchImpl(path);
1141
+ if (!response.ok) {
1142
+ throw new Error(`Voice platform coverage failed: HTTP ${response.status}`);
1143
+ }
1144
+ return await response.json();
1145
+ };
1146
+ var createVoicePlatformCoverageStore = (path = "/api/voice/platform-coverage", options = {}) => {
1147
+ const listeners = new Set;
1148
+ let closed = false;
1149
+ let timer;
1150
+ let snapshot = {
1151
+ error: null,
1152
+ isLoading: false
1153
+ };
1154
+ const emit = () => {
1155
+ for (const listener of listeners) {
1156
+ listener();
1157
+ }
1158
+ };
1159
+ const refresh = async () => {
1160
+ if (closed) {
1161
+ return snapshot.report;
1162
+ }
1163
+ snapshot = {
1164
+ ...snapshot,
1165
+ error: null,
1166
+ isLoading: true
1167
+ };
1168
+ emit();
1169
+ try {
1170
+ const report = await fetchVoicePlatformCoverage(path, options);
1171
+ snapshot = {
1172
+ error: null,
1173
+ isLoading: false,
1174
+ report,
1175
+ updatedAt: Date.now()
1176
+ };
1177
+ emit();
1178
+ return report;
1179
+ } catch (error) {
1180
+ snapshot = {
1181
+ ...snapshot,
1182
+ error: error instanceof Error ? error.message : String(error),
1183
+ isLoading: false
1184
+ };
1185
+ emit();
1186
+ throw error;
1187
+ }
1188
+ };
1189
+ const close = () => {
1190
+ closed = true;
1191
+ if (timer) {
1192
+ clearInterval(timer);
1193
+ timer = undefined;
1194
+ }
1195
+ listeners.clear();
1196
+ };
1197
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
1198
+ timer = setInterval(() => {
1199
+ refresh().catch(() => {});
1200
+ }, options.intervalMs);
1201
+ }
1202
+ return {
1203
+ close,
1204
+ getServerSnapshot: () => snapshot,
1205
+ getSnapshot: () => snapshot,
1206
+ refresh,
1207
+ subscribe: (listener) => {
1208
+ listeners.add(listener);
1209
+ return () => {
1210
+ listeners.delete(listener);
1211
+ };
1212
+ }
1213
+ };
1214
+ };
1215
+
1216
+ // src/vue/useVoicePlatformCoverage.ts
1217
+ function useVoicePlatformCoverage(path = "/api/voice/platform-coverage", options = {}) {
1218
+ const store = createVoicePlatformCoverageStore(path, options);
1219
+ const error = ref4(null);
1220
+ const isLoading = ref4(false);
1221
+ const report = shallowRef4(undefined);
1222
+ const updatedAt = ref4(undefined);
1223
+ const sync = () => {
1224
+ const snapshot = store.getSnapshot();
1225
+ error.value = snapshot.error;
1226
+ isLoading.value = snapshot.isLoading;
1227
+ report.value = snapshot.report;
1228
+ updatedAt.value = snapshot.updatedAt;
1229
+ };
1230
+ const unsubscribe = store.subscribe(sync);
1231
+ sync();
1232
+ if (typeof window !== "undefined") {
1233
+ store.refresh().catch(() => {});
1234
+ }
1235
+ onUnmounted4(() => {
1236
+ unsubscribe();
1237
+ store.close();
1238
+ });
1239
+ return {
1240
+ error,
1241
+ isLoading,
1242
+ refresh: store.refresh,
1243
+ report,
1244
+ updatedAt
1245
+ };
1246
+ }
1247
+
1248
+ // src/client/platformCoverageWidget.ts
1249
+ var DEFAULT_TITLE4 = "Platform Replacement Coverage";
1250
+ var DEFAULT_DESCRIPTION4 = "Code-owned coverage for hosted voice-platform surfaces, backed by the same proof routes used by release evidence.";
1251
+ var DEFAULT_LINKS = [
1252
+ { href: "/switching-from-vapi", label: "Switching guide" },
1253
+ { href: "/api/voice/vapi-coverage", label: "Coverage JSON" }
1254
+ ];
1255
+ var escapeHtml4 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1256
+ var formatStatus = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
1257
+ var surfaceDetail2 = (surface) => {
1258
+ if (surface.status === "pass") {
1259
+ return surface.replacement;
1260
+ }
1261
+ if (surface.gap) {
1262
+ return surface.gap;
1263
+ }
1264
+ if (surface.missingEvidence?.length) {
1265
+ return `Missing evidence: ${surface.missingEvidence.join(", ")}`;
1266
+ }
1267
+ return surface.replacement;
1268
+ };
1269
+ var createVoicePlatformCoverageViewModel = (snapshot, options = {}) => {
1270
+ const allSurfaces = snapshot.report?.coverage ?? [];
1271
+ const failing = allSurfaces.filter((surface) => surface.status !== "pass");
1272
+ const limit = options.limit ?? 6;
1273
+ const surfaces = allSurfaces.slice(0, limit).map((surface) => ({
1274
+ ...surface,
1275
+ detail: surfaceDetail2(surface),
1276
+ label: surface.surface
1277
+ }));
1278
+ return {
1279
+ description: options.description ?? DEFAULT_DESCRIPTION4,
1280
+ error: snapshot.error,
1281
+ isLoading: snapshot.isLoading,
1282
+ label: snapshot.error ? "Unavailable" : snapshot.report ? failing.length ? `${failing.length} gaps` : `${snapshot.report.total} surfaces passing` : snapshot.isLoading ? "Checking" : "No coverage report",
1283
+ links: options.links ?? DEFAULT_LINKS,
1284
+ status: snapshot.error ? "error" : snapshot.report ? failing.length ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
1285
+ surfaces,
1286
+ title: options.title ?? DEFAULT_TITLE4,
1287
+ updatedAt: snapshot.updatedAt
1288
+ };
1289
+ };
1290
+ var renderVoicePlatformCoverageHTML = (snapshot, options = {}) => {
1291
+ const model = createVoicePlatformCoverageViewModel(snapshot, options);
1292
+ const surfaces = model.surfaces.length ? `<div class="absolute-voice-platform-coverage__surfaces">${model.surfaces.map((surface) => `<article class="absolute-voice-platform-coverage__surface absolute-voice-platform-coverage__surface--${escapeHtml4(surface.status)}">
1293
+ <header>
1294
+ <strong>${escapeHtml4(surface.label)}</strong>
1295
+ <span>${escapeHtml4(formatStatus(surface.status))}</span>
1296
+ </header>
1297
+ <p>${escapeHtml4(surface.detail)}</p>
1298
+ <small>${surface.evidence.filter((item) => item.ok).length}/${surface.evidence.length} evidence checks passing</small>
1299
+ </article>`).join("")}</div>` : `<p class="absolute-voice-platform-coverage__empty">${model.error ? escapeHtml4(model.error) : "Run the proof pack to populate platform coverage evidence."}</p>`;
1300
+ const links = model.links.length ? `<p class="absolute-voice-platform-coverage__links">${model.links.map((link) => `<a href="${escapeHtml4(link.href)}">${escapeHtml4(link.label)}</a>`).join("")}</p>` : "";
1301
+ return `<section class="absolute-voice-platform-coverage absolute-voice-platform-coverage--${escapeHtml4(model.status)}">
1302
+ <header class="absolute-voice-platform-coverage__header">
1303
+ <span class="absolute-voice-platform-coverage__eyebrow">${escapeHtml4(model.title)}</span>
1304
+ <strong class="absolute-voice-platform-coverage__label">${escapeHtml4(model.label)}</strong>
1305
+ </header>
1306
+ <p class="absolute-voice-platform-coverage__description">${escapeHtml4(model.description)}</p>
1307
+ ${surfaces}
1308
+ ${links}
1309
+ ${model.error ? `<p class="absolute-voice-platform-coverage__error">${escapeHtml4(model.error)}</p>` : ""}
1310
+ </section>`;
1311
+ };
1312
+ var getVoicePlatformCoverageCSS = () => `.absolute-voice-platform-coverage{border:1px solid #c7d2fe;border-radius:20px;background:#f8fbff;color:#111827;padding:18px;box-shadow:0 18px 40px rgba(30,64,175,.12);font-family:inherit}.absolute-voice-platform-coverage--warning,.absolute-voice-platform-coverage--error{border-color:#f2a7a7;background:#fff7f4}.absolute-voice-platform-coverage__header,.absolute-voice-platform-coverage__surface header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-platform-coverage__eyebrow{color:#1d4ed8;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-platform-coverage__label{font-size:24px;line-height:1}.absolute-voice-platform-coverage__description,.absolute-voice-platform-coverage__surface p,.absolute-voice-platform-coverage__surface small,.absolute-voice-platform-coverage__empty{color:#475569}.absolute-voice-platform-coverage__surfaces{display:grid;gap:10px;margin-top:14px}.absolute-voice-platform-coverage__surface{background:#fff;border:1px solid #dbeafe;border-radius:16px;padding:12px}.absolute-voice-platform-coverage__surface--pass{border-color:#86efac}.absolute-voice-platform-coverage__surface--fail,.absolute-voice-platform-coverage__surface--missing,.absolute-voice-platform-coverage__surface--stale{border-color:#f2a7a7}.absolute-voice-platform-coverage__surface p{margin:8px 0}.absolute-voice-platform-coverage__surface span{text-transform:capitalize}.absolute-voice-platform-coverage__links{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0 0}.absolute-voice-platform-coverage__links a{border:1px solid #bfdbfe;border-radius:999px;color:#1d4ed8;font-weight:800;padding:6px 10px;text-decoration:none}.absolute-voice-platform-coverage__error{color:#9f1239;font-weight:700}`;
1313
+ var mountVoicePlatformCoverage = (element, path = "/api/voice/platform-coverage", options = {}) => {
1314
+ const store = createVoicePlatformCoverageStore(path, options);
1315
+ const render = () => {
1316
+ element.innerHTML = renderVoicePlatformCoverageHTML(store.getSnapshot(), options);
1317
+ };
1318
+ const unsubscribe = store.subscribe(render);
1319
+ render();
1320
+ store.refresh().catch(() => {});
1321
+ return {
1322
+ close: () => {
1323
+ unsubscribe();
1324
+ store.close();
1325
+ },
1326
+ refresh: store.refresh
1327
+ };
1328
+ };
1329
+ var defineVoicePlatformCoverageElement = (tagName = "absolute-voice-platform-coverage") => {
1330
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
1331
+ return;
1332
+ }
1333
+ customElements.define(tagName, class AbsoluteVoicePlatformCoverageElement extends HTMLElement {
1334
+ mounted;
1335
+ connectedCallback() {
1336
+ this.mounted = mountVoicePlatformCoverage(this, this.getAttribute("path") ?? "/api/voice/platform-coverage", {
1337
+ description: this.getAttribute("description") ?? undefined,
1338
+ intervalMs: Number(this.getAttribute("interval-ms") ?? 0) || undefined,
1339
+ limit: Number(this.getAttribute("limit") ?? 0) || undefined,
1340
+ title: this.getAttribute("title") ?? undefined
1341
+ });
1342
+ }
1343
+ disconnectedCallback() {
1344
+ this.mounted?.close();
1345
+ this.mounted = undefined;
1346
+ }
1347
+ });
1348
+ };
1349
+
1350
+ // src/vue/VoicePlatformCoverage.ts
1351
+ var VoicePlatformCoverage = defineComponent4({
1352
+ name: "VoicePlatformCoverage",
1353
+ props: {
1354
+ description: String,
1355
+ intervalMs: Number,
1356
+ limit: Number,
1357
+ path: {
1358
+ default: "/api/voice/platform-coverage",
1359
+ type: String
1360
+ },
1361
+ title: String
1362
+ },
1363
+ setup(props) {
1364
+ const state = useVoicePlatformCoverage(props.path, {
1365
+ description: props.description,
1366
+ intervalMs: props.intervalMs,
1367
+ limit: props.limit,
1368
+ title: props.title
1369
+ });
1370
+ return () => {
1371
+ const model = createVoicePlatformCoverageViewModel({
1372
+ error: state.error.value,
1373
+ isLoading: state.isLoading.value,
1374
+ report: state.report.value,
1375
+ updatedAt: state.updatedAt.value
1376
+ }, {
1377
+ description: props.description,
1378
+ intervalMs: props.intervalMs,
1379
+ limit: props.limit,
1380
+ title: props.title
1381
+ });
1382
+ return h4("section", {
1383
+ class: [
1384
+ "absolute-voice-platform-coverage",
1385
+ `absolute-voice-platform-coverage--${model.status}`
1386
+ ]
1387
+ }, [
1388
+ h4("header", { class: "absolute-voice-platform-coverage__header" }, [
1389
+ h4("span", { class: "absolute-voice-platform-coverage__eyebrow" }, model.title),
1390
+ h4("strong", { class: "absolute-voice-platform-coverage__label" }, model.label)
1391
+ ]),
1392
+ h4("p", { class: "absolute-voice-platform-coverage__description" }, model.description),
1393
+ model.surfaces.length ? h4("div", { class: "absolute-voice-platform-coverage__surfaces" }, model.surfaces.map((surface) => h4("article", {
1394
+ class: [
1395
+ "absolute-voice-platform-coverage__surface",
1396
+ `absolute-voice-platform-coverage__surface--${surface.status}`
1397
+ ],
1398
+ key: surface.surface
1399
+ }, [
1400
+ h4("header", [
1401
+ h4("strong", surface.label),
1402
+ h4("span", surface.status)
1403
+ ]),
1404
+ h4("p", surface.detail),
1405
+ h4("small", `${surface.evidence.filter((item) => item.ok).length}/${surface.evidence.length} evidence checks passing`)
1406
+ ]))) : h4("p", { class: "absolute-voice-platform-coverage__empty" }, model.error ?? "Run the proof pack to populate platform coverage evidence."),
1407
+ model.links.length ? h4("p", { class: "absolute-voice-platform-coverage__links" }, model.links.map((link) => h4("a", { href: link.href, key: link.href }, link.label))) : null,
1408
+ model.error ? h4("p", { class: "absolute-voice-platform-coverage__error" }, model.error) : null
1409
+ ]);
1410
+ };
1411
+ }
1412
+ });
1413
+ // src/vue/VoiceProofTrends.ts
1414
+ import { defineComponent as defineComponent5, h as h5 } from "vue";
1415
+
1416
+ // src/proofTrends.ts
1417
+ import { Elysia } from "elysia";
1418
+ var DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS = 24 * 60 * 60 * 1000;
1419
+ var normalizeMaxAgeMs = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS;
1420
+ var toTimeMs = (value) => {
1421
+ if (value instanceof Date) {
1422
+ return value.getTime();
1423
+ }
1424
+ if (typeof value === "number") {
1425
+ return value;
1426
+ }
1427
+ if (typeof value === "string") {
1428
+ return Date.parse(value);
1429
+ }
1430
+ return Date.now();
1431
+ };
1432
+ var buildVoiceProofTrendReport = (input = {}) => {
1433
+ const maxAgeMs = normalizeMaxAgeMs(input.maxAgeMs);
1434
+ const nowMs = toTimeMs(input.now);
1435
+ const generatedAtMs = typeof input.generatedAt === "string" ? Date.parse(input.generatedAt) : Number.NaN;
1436
+ const ageMs = Number.isFinite(generatedAtMs) && Number.isFinite(nowMs) ? Math.max(0, nowMs - generatedAtMs) : undefined;
1437
+ const freshUntil = Number.isFinite(generatedAtMs) && Number.isFinite(maxAgeMs) ? new Date(generatedAtMs + maxAgeMs).toISOString() : undefined;
1438
+ const isFresh = ageMs !== undefined && ageMs <= maxAgeMs;
1439
+ const status = input.status === "empty" ? "empty" : !isFresh ? "stale" : input.ok === true ? "pass" : "fail";
1440
+ return {
1441
+ ageMs,
1442
+ baseUrl: input.baseUrl,
1443
+ cycles: input.cycles ?? [],
1444
+ freshUntil,
1445
+ generatedAt: input.generatedAt,
1446
+ maxAgeMs,
1447
+ ok: input.ok === true && status === "pass",
1448
+ outputDir: input.outputDir,
1449
+ runId: input.runId,
1450
+ source: input.source ?? "",
1451
+ status,
1452
+ summary: input.summary ?? {}
1453
+ };
1454
+ };
1455
+ var buildEmptyVoiceProofTrendReport = (source = "", maxAgeMs) => buildVoiceProofTrendReport({
1456
+ maxAgeMs,
1457
+ source,
1458
+ status: "empty"
1459
+ });
1460
+ var normalizeVoiceProofTrendReport = (value, options = {}) => {
1461
+ if ("status" in value && value.status === "empty") {
1462
+ return buildEmptyVoiceProofTrendReport(value.source || options.source || "", options.maxAgeMs ?? value.maxAgeMs);
1463
+ }
1464
+ return buildVoiceProofTrendReport({
1465
+ ...value,
1466
+ maxAgeMs: options.maxAgeMs ?? value.maxAgeMs,
1467
+ source: value.source ?? options.source
1468
+ });
1469
+ };
1470
+ var readVoiceProofTrendReportFile = async (path, options = {}) => {
1471
+ const file = Bun.file(path);
1472
+ if (!await file.exists()) {
1473
+ return buildEmptyVoiceProofTrendReport(path, options.maxAgeMs);
1474
+ }
1475
+ try {
1476
+ const parsed = await file.json();
1477
+ return normalizeVoiceProofTrendReport(parsed, {
1478
+ maxAgeMs: options.maxAgeMs,
1479
+ source: path
1480
+ });
1481
+ } catch {
1482
+ return buildVoiceProofTrendReport({
1483
+ maxAgeMs: options.maxAgeMs,
1484
+ source: path
1485
+ });
1486
+ }
1487
+ };
1488
+ var maxNumber = (values) => {
1489
+ const finite = values.filter((value) => typeof value === "number" && Number.isFinite(value));
1490
+ return finite.length > 0 ? Math.max(...finite) : undefined;
1491
+ };
1492
+ var readProofTrendMaxLiveP95 = (report) => report.summary.maxLiveP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.liveLatency?.p95Ms));
1493
+ var readProofTrendMaxProviderP95 = (report) => report.summary.maxProviderP95Ms;
1494
+ var readProofTrendMaxTurnP95 = (report) => report.summary.maxTurnP95Ms ?? maxNumber(report.cycles.map((cycle) => cycle.turnLatency?.p95Ms));
1495
+ var evaluateVoiceProofTrendEvidence = (report, input = {}) => {
1496
+ const issues = [];
1497
+ const requiredStatus = input.requireStatus ?? "pass";
1498
+ const minCycles = input.minCycles ?? 1;
1499
+ const requireAllCyclesOk = input.requireAllCyclesOk ?? true;
1500
+ const cycles = report.summary.cycles ?? report.cycles.length;
1501
+ const failedCycles = report.cycles.filter((cycle) => cycle.ok !== true).length;
1502
+ const maxLiveP95Ms = readProofTrendMaxLiveP95(report);
1503
+ const maxProviderP95Ms = readProofTrendMaxProviderP95(report);
1504
+ const maxTurnP95Ms = readProofTrendMaxTurnP95(report);
1505
+ if (report.status !== requiredStatus) {
1506
+ issues.push(`Expected proof trends status ${requiredStatus}, found ${report.status}.`);
1507
+ }
1508
+ if (report.ok !== true) {
1509
+ issues.push("Expected proof trends ok to be true.");
1510
+ }
1511
+ if (cycles < minCycles) {
1512
+ issues.push(`Expected at least ${String(minCycles)} proof trend cycle(s), found ${String(cycles)}.`);
1513
+ }
1514
+ if (requireAllCyclesOk && failedCycles > 0) {
1515
+ issues.push(`Expected all proof trend cycles to pass, found ${String(failedCycles)} failing cycle(s).`);
1516
+ }
1517
+ if (input.maxAgeMs !== undefined && (report.ageMs === undefined || report.ageMs > input.maxAgeMs)) {
1518
+ issues.push(report.ageMs === undefined ? "Missing proof trends artifact age." : `Expected proof trends age at most ${String(input.maxAgeMs)}ms, found ${String(report.ageMs)}ms.`);
1519
+ }
1520
+ if (input.maxLiveP95Ms !== undefined && (maxLiveP95Ms === undefined || maxLiveP95Ms > input.maxLiveP95Ms)) {
1521
+ issues.push(maxLiveP95Ms === undefined ? "Missing proof trends live latency p95." : `Expected proof trends live latency p95 at most ${String(input.maxLiveP95Ms)}ms, found ${String(maxLiveP95Ms)}ms.`);
1522
+ }
1523
+ if (input.maxProviderP95Ms !== undefined && (maxProviderP95Ms === undefined || maxProviderP95Ms > input.maxProviderP95Ms)) {
1524
+ issues.push(maxProviderP95Ms === undefined ? "Missing proof trends provider p95." : `Expected proof trends provider p95 at most ${String(input.maxProviderP95Ms)}ms, found ${String(maxProviderP95Ms)}ms.`);
1525
+ }
1526
+ if (input.maxTurnP95Ms !== undefined && (maxTurnP95Ms === undefined || maxTurnP95Ms > input.maxTurnP95Ms)) {
1527
+ issues.push(maxTurnP95Ms === undefined ? "Missing proof trends turn latency p95." : `Expected proof trends turn latency p95 at most ${String(input.maxTurnP95Ms)}ms, found ${String(maxTurnP95Ms)}ms.`);
1528
+ }
1529
+ if (input.minLiveLatencySamples !== undefined) {
1530
+ const lowSamples = report.cycles.filter((cycle) => (cycle.liveLatency?.samples ?? 0) < input.minLiveLatencySamples).length;
1531
+ if (lowSamples > 0) {
1532
+ issues.push(`Expected every proof trend cycle to have at least ${String(input.minLiveLatencySamples)} live latency sample(s), found ${String(lowSamples)} low-sample cycle(s).`);
1533
+ }
1534
+ }
1535
+ if (input.minProviderSloEventsWithLatency !== undefined) {
1536
+ const lowSamples = report.cycles.filter((cycle) => (cycle.providerSlo?.eventsWithLatency ?? 0) < input.minProviderSloEventsWithLatency).length;
1537
+ if (lowSamples > 0) {
1538
+ issues.push(`Expected every proof trend cycle to have at least ${String(input.minProviderSloEventsWithLatency)} provider latency event(s), found ${String(lowSamples)} low-sample cycle(s).`);
1539
+ }
1540
+ }
1541
+ if (input.minTurnLatencySamples !== undefined) {
1542
+ const lowSamples = report.cycles.filter((cycle) => (cycle.turnLatency?.samples ?? 0) < input.minTurnLatencySamples).length;
1543
+ if (lowSamples > 0) {
1544
+ issues.push(`Expected every proof trend cycle to have at least ${String(input.minTurnLatencySamples)} turn latency sample(s), found ${String(lowSamples)} low-sample cycle(s).`);
1545
+ }
1546
+ }
1547
+ return {
1548
+ ageMs: report.ageMs,
1549
+ cycles,
1550
+ failedCycles,
1551
+ issues,
1552
+ maxLiveP95Ms,
1553
+ maxProviderP95Ms,
1554
+ maxTurnP95Ms,
1555
+ ok: issues.length === 0,
1556
+ status: report.status
1557
+ };
1558
+ };
1559
+ var assertVoiceProofTrendEvidence = (report, input = {}) => {
1560
+ const assertion = evaluateVoiceProofTrendEvidence(report, input);
1561
+ if (!assertion.ok) {
1562
+ throw new Error(`Voice proof trends assertion failed: ${assertion.issues.join(" ")}`);
1563
+ }
1564
+ return assertion;
1565
+ };
1566
+ var createVoiceProofTrendRoutes = (options) => {
1567
+ const path = options.path ?? "/api/voice/proof-trends";
1568
+ const routes = new Elysia({
1569
+ name: options.name ?? "absolutejs-voice-proof-trends"
1570
+ });
1571
+ routes.get(path, async () => {
1572
+ const value = options.source !== undefined ? typeof options.source === "function" ? await options.source() : options.source : options.jsonPath ? await readVoiceProofTrendReportFile(options.jsonPath, {
1573
+ maxAgeMs: options.maxAgeMs
1574
+ }) : buildEmptyVoiceProofTrendReport("", options.maxAgeMs);
1575
+ return Response.json(normalizeVoiceProofTrendReport(value, {
1576
+ maxAgeMs: options.maxAgeMs,
1577
+ source: options.jsonPath
1578
+ }), { headers: options.headers });
1579
+ });
1580
+ return routes;
1581
+ };
1582
+ var formatVoiceProofTrendAge = (ageMs) => {
1583
+ if (typeof ageMs !== "number" || !Number.isFinite(ageMs)) {
1584
+ return "unknown";
1585
+ }
1586
+ const minutes = Math.floor(ageMs / 60000);
1587
+ if (minutes < 1) {
1588
+ return "less than 1m";
1589
+ }
1590
+ if (minutes < 60) {
1591
+ return `${minutes}m`;
1592
+ }
1593
+ const hours = Math.floor(minutes / 60);
1594
+ if (hours < 48) {
1595
+ return `${hours}h ${minutes % 60}m`;
1596
+ }
1597
+ const days = Math.floor(hours / 24);
1598
+ return `${days}d ${hours % 24}h`;
1599
+ };
1600
+
1601
+ // src/client/proofTrends.ts
1602
+ var fetchVoiceProofTrends = async (path = "/api/voice/proof-trends", options = {}) => {
1603
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1604
+ const response = await fetchImpl(path);
1605
+ if (!response.ok) {
1606
+ throw new Error(`Voice proof trends failed: HTTP ${response.status}`);
1607
+ }
1608
+ return await response.json();
1609
+ };
1610
+ var createVoiceProofTrendsStore = (path = "/api/voice/proof-trends", options = {}) => {
1611
+ const listeners = new Set;
1612
+ let closed = false;
1613
+ let timer;
1614
+ let snapshot = {
1615
+ error: null,
1616
+ isLoading: false
1617
+ };
1618
+ const emit = () => {
1619
+ for (const listener of listeners) {
1620
+ listener();
1621
+ }
1622
+ };
1623
+ const refresh = async () => {
1624
+ if (closed) {
1625
+ return snapshot.report;
1626
+ }
1627
+ snapshot = {
1628
+ ...snapshot,
1629
+ error: null,
1630
+ isLoading: true
1631
+ };
1632
+ emit();
1633
+ try {
1634
+ const report = await fetchVoiceProofTrends(path, options);
1635
+ snapshot = {
1636
+ error: null,
1637
+ isLoading: false,
1638
+ report,
1639
+ updatedAt: Date.now()
1640
+ };
1641
+ emit();
1642
+ return report;
1643
+ } catch (error) {
1644
+ snapshot = {
1645
+ ...snapshot,
1646
+ error: error instanceof Error ? error.message : String(error),
1647
+ isLoading: false
1648
+ };
1649
+ emit();
1650
+ throw error;
1651
+ }
1652
+ };
1653
+ const close = () => {
1654
+ closed = true;
1655
+ if (timer) {
1656
+ clearInterval(timer);
1657
+ timer = undefined;
1658
+ }
1659
+ listeners.clear();
1660
+ };
1661
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
1662
+ timer = setInterval(() => {
1663
+ refresh().catch(() => {});
1664
+ }, options.intervalMs);
1665
+ }
1666
+ return {
1667
+ close,
1668
+ getServerSnapshot: () => snapshot,
1669
+ getSnapshot: () => snapshot,
1670
+ refresh,
1671
+ subscribe: (listener) => {
1672
+ listeners.add(listener);
1673
+ return () => {
1674
+ listeners.delete(listener);
1675
+ };
1676
+ }
1677
+ };
1678
+ };
1679
+
1680
+ // src/client/proofTrendsWidget.ts
1681
+ var DEFAULT_TITLE5 = "Sustained Proof Trends";
1682
+ var DEFAULT_DESCRIPTION5 = "Repeated-cycle provider, latency, recovery, and readiness evidence with freshness gating.";
1683
+ var DEFAULT_LINKS2 = [
1684
+ { href: "/voice/proof-trends", label: "Trend page" },
1685
+ { href: "/api/voice/proof-trends", label: "Trend JSON" }
1686
+ ];
1687
+ var escapeHtml5 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1688
+ var formatMs = (value) => typeof value === "number" && Number.isFinite(value) ? `${Math.round(value)}ms` : "n/a";
1689
+ var statusLabel = (report) => {
1690
+ if (!report) {
1691
+ return "No trend report";
1692
+ }
1693
+ if (report.status === "pass") {
1694
+ return `${report.summary.cycles ?? report.cycles.length} cycles passing`;
1695
+ }
1696
+ return report.status;
1697
+ };
1698
+ var createVoiceProofTrendsViewModel = (snapshot, options = {}) => {
1699
+ const report = snapshot.report;
1700
+ const metrics = report ? [
1701
+ { label: "Status", value: report.status.toUpperCase() },
1702
+ {
1703
+ label: "Cycles",
1704
+ value: String(report.summary.cycles ?? report.cycles.length)
1705
+ },
1706
+ {
1707
+ label: "Provider p95",
1708
+ value: formatMs(report.summary.maxProviderP95Ms)
1709
+ },
1710
+ { label: "Turn p95", value: formatMs(report.summary.maxTurnP95Ms) },
1711
+ { label: "Live p95", value: formatMs(report.summary.maxLiveP95Ms) },
1712
+ {
1713
+ label: "Artifact age",
1714
+ value: formatVoiceProofTrendAge(report.ageMs)
1715
+ },
1716
+ {
1717
+ label: "Stale after",
1718
+ value: formatVoiceProofTrendAge(report.maxAgeMs)
1719
+ }
1720
+ ] : [];
1721
+ return {
1722
+ description: options.description ?? DEFAULT_DESCRIPTION5,
1723
+ error: snapshot.error,
1724
+ isLoading: snapshot.isLoading,
1725
+ label: snapshot.error ? "Unavailable" : report ? statusLabel(report) : snapshot.isLoading ? "Checking" : "No trend report",
1726
+ links: options.links ?? DEFAULT_LINKS2,
1727
+ metrics,
1728
+ report,
1729
+ status: snapshot.error ? "error" : report ? report.status === "pass" ? "ready" : "warning" : snapshot.isLoading ? "loading" : "empty",
1730
+ title: options.title ?? DEFAULT_TITLE5,
1731
+ updatedAt: snapshot.updatedAt
1732
+ };
1733
+ };
1734
+ var renderVoiceProofTrendsHTML = (snapshot, options = {}) => {
1735
+ const model = createVoiceProofTrendsViewModel(snapshot, options);
1736
+ const metrics = model.metrics.length ? `<div class="absolute-voice-proof-trends__metrics">${model.metrics.map((metric) => `<article>
1737
+ <span>${escapeHtml5(metric.label)}</span>
1738
+ <strong>${escapeHtml5(metric.value)}</strong>
1739
+ </article>`).join("")}</div>` : `<p class="absolute-voice-proof-trends__empty">${model.error ? escapeHtml5(model.error) : "Run the sustained proof trends script to populate evidence."}</p>`;
1740
+ const links = model.links.length ? `<p class="absolute-voice-proof-trends__links">${model.links.map((link) => `<a href="${escapeHtml5(link.href)}">${escapeHtml5(link.label)}</a>`).join("")}</p>` : "";
1741
+ return `<section class="absolute-voice-proof-trends absolute-voice-proof-trends--${escapeHtml5(model.status)}">
1742
+ <header class="absolute-voice-proof-trends__header">
1743
+ <span class="absolute-voice-proof-trends__eyebrow">${escapeHtml5(model.title)}</span>
1744
+ <strong class="absolute-voice-proof-trends__label">${escapeHtml5(model.label)}</strong>
1745
+ </header>
1746
+ <p class="absolute-voice-proof-trends__description">${escapeHtml5(model.description)}</p>
1747
+ ${metrics}
1748
+ ${links}
1749
+ ${model.error ? `<p class="absolute-voice-proof-trends__error">${escapeHtml5(model.error)}</p>` : ""}
1750
+ </section>`;
1751
+ };
1752
+ var getVoiceProofTrendsCSS = () => `.absolute-voice-proof-trends{border:1px solid #99f6e4;border-radius:20px;background:#f0fdfa;color:#0f172a;padding:18px;box-shadow:0 18px 40px rgba(13,148,136,.12);font-family:inherit}.absolute-voice-proof-trends--warning,.absolute-voice-proof-trends--error{border-color:#f2a7a7;background:#fff7f4}.absolute-voice-proof-trends__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-proof-trends__eyebrow{color:#0f766e;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-proof-trends__label{font-size:24px;line-height:1}.absolute-voice-proof-trends__description,.absolute-voice-proof-trends__empty{color:#475569}.absolute-voice-proof-trends__metrics{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));margin-top:14px}.absolute-voice-proof-trends__metrics article{background:#fff;border:1px solid #ccfbf1;border-radius:16px;padding:12px}.absolute-voice-proof-trends__metrics span{color:#64748b;display:block;font-size:12px;font-weight:800;text-transform:uppercase}.absolute-voice-proof-trends__metrics strong{display:block;font-size:20px;margin-top:4px}.absolute-voice-proof-trends__links{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0 0}.absolute-voice-proof-trends__links a{border:1px solid #99f6e4;border-radius:999px;color:#0f766e;font-weight:800;padding:6px 10px;text-decoration:none}.absolute-voice-proof-trends__error{color:#9f1239;font-weight:700}`;
1753
+ var mountVoiceProofTrends = (element, path = "/api/voice/proof-trends", options = {}) => {
1754
+ const store = createVoiceProofTrendsStore(path, options);
1755
+ const render = () => {
1756
+ element.innerHTML = renderVoiceProofTrendsHTML(store.getSnapshot(), options);
1757
+ };
1758
+ const unsubscribe = store.subscribe(render);
1759
+ render();
1760
+ store.refresh().catch(() => {});
1761
+ return {
1762
+ close: () => {
1763
+ unsubscribe();
1764
+ store.close();
1765
+ },
1766
+ refresh: store.refresh
1767
+ };
1768
+ };
1769
+ var defineVoiceProofTrendsElement = (tagName = "absolute-voice-proof-trends") => {
1770
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
1771
+ return;
1772
+ }
1773
+ customElements.define(tagName, class AbsoluteVoiceProofTrendsElement extends HTMLElement {
1774
+ mounted;
1775
+ connectedCallback() {
1776
+ this.mounted = mountVoiceProofTrends(this, this.getAttribute("path") ?? "/api/voice/proof-trends", {
1777
+ description: this.getAttribute("description") ?? undefined,
1778
+ intervalMs: Number(this.getAttribute("interval-ms") ?? 0) || undefined,
1779
+ title: this.getAttribute("title") ?? undefined
1780
+ });
1781
+ }
1782
+ disconnectedCallback() {
1783
+ this.mounted?.close();
1784
+ this.mounted = undefined;
1785
+ }
1786
+ });
1787
+ };
1788
+
1789
+ // src/vue/useVoiceProofTrends.ts
1790
+ import { onUnmounted as onUnmounted5, ref as ref5, shallowRef as shallowRef5 } from "vue";
1791
+ function useVoiceProofTrends(path = "/api/voice/proof-trends", options = {}) {
1792
+ const store = createVoiceProofTrendsStore(path, options);
1793
+ const error = ref5(null);
1794
+ const isLoading = ref5(false);
1795
+ const report = shallowRef5(undefined);
1796
+ const updatedAt = ref5(undefined);
1797
+ const sync = () => {
1798
+ const snapshot = store.getSnapshot();
1799
+ error.value = snapshot.error;
1800
+ isLoading.value = snapshot.isLoading;
1801
+ report.value = snapshot.report;
1802
+ updatedAt.value = snapshot.updatedAt;
1803
+ };
1804
+ const unsubscribe = store.subscribe(sync);
1805
+ sync();
1806
+ if (typeof window !== "undefined") {
1807
+ store.refresh().catch(() => {});
1808
+ }
1809
+ onUnmounted5(() => {
1810
+ unsubscribe();
1811
+ store.close();
1812
+ });
1813
+ return {
1814
+ error,
1815
+ isLoading,
1816
+ refresh: store.refresh,
1817
+ report,
1818
+ updatedAt
1819
+ };
1820
+ }
1821
+
1822
+ // src/vue/VoiceProofTrends.ts
1823
+ var VoiceProofTrends = defineComponent5({
1824
+ name: "VoiceProofTrends",
1825
+ props: {
1826
+ description: String,
1827
+ intervalMs: Number,
1828
+ path: {
1829
+ default: "/api/voice/proof-trends",
1830
+ type: String
1831
+ },
1832
+ title: String
1833
+ },
1834
+ setup(props) {
1835
+ const state = useVoiceProofTrends(props.path, {
1836
+ description: props.description,
1837
+ intervalMs: props.intervalMs,
1838
+ title: props.title
1839
+ });
1840
+ return () => {
1841
+ const model = createVoiceProofTrendsViewModel({
1842
+ error: state.error.value,
1843
+ isLoading: state.isLoading.value,
1844
+ report: state.report.value,
1845
+ updatedAt: state.updatedAt.value
1846
+ }, {
1847
+ description: props.description,
1848
+ intervalMs: props.intervalMs,
1849
+ title: props.title
1850
+ });
1851
+ return h5("section", {
1852
+ class: [
1853
+ "absolute-voice-proof-trends",
1854
+ `absolute-voice-proof-trends--${model.status}`
1855
+ ]
1856
+ }, [
1857
+ h5("header", { class: "absolute-voice-proof-trends__header" }, [
1858
+ h5("span", { class: "absolute-voice-proof-trends__eyebrow" }, model.title),
1859
+ h5("strong", { class: "absolute-voice-proof-trends__label" }, model.label)
1860
+ ]),
1861
+ h5("p", { class: "absolute-voice-proof-trends__description" }, model.description),
1862
+ model.metrics.length ? h5("div", { class: "absolute-voice-proof-trends__metrics" }, model.metrics.map((metric) => h5("article", { key: metric.label }, [
1863
+ h5("span", metric.label),
1864
+ h5("strong", metric.value)
1865
+ ]))) : h5("p", { class: "absolute-voice-proof-trends__empty" }, model.error ?? "Run the sustained proof trends script to populate evidence."),
1866
+ model.links.length ? h5("p", { class: "absolute-voice-proof-trends__links" }, model.links.map((link) => h5("a", { href: link.href, key: link.href }, link.label))) : null,
1867
+ model.error ? h5("p", { class: "absolute-voice-proof-trends__error" }, model.error) : null
1868
+ ]);
1869
+ };
1870
+ }
1871
+ });
1872
+ // src/vue/VoiceProviderSimulationControls.ts
1873
+ import { computed, defineComponent as defineComponent6, h as h6 } from "vue";
1874
+
1875
+ // src/client/providerSimulationControls.ts
1876
+ var postSimulation = async (pathPrefix, mode, provider, fetchImpl) => {
1877
+ const response = await fetchImpl(`${pathPrefix}/${mode}?provider=${encodeURIComponent(provider)}`, { method: "POST" });
1878
+ const body = await response.json().catch(() => null);
1879
+ if (!response.ok) {
1880
+ const message = body && typeof body === "object" && "error" in body ? String(body.error) : `Voice provider simulation failed: HTTP ${response.status}`;
1881
+ throw new Error(message);
1882
+ }
1883
+ return body;
1884
+ };
1885
+ var createVoiceProviderSimulationControlsStore = (options) => {
1886
+ const listeners = new Set;
1887
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1888
+ const pathPrefix = options.pathPrefix ?? `/api/${options.kind ?? "stt"}-simulate`;
1889
+ let closed = false;
1890
+ let snapshot = {
1891
+ error: null,
1892
+ isRunning: false,
1893
+ lastResult: null,
1894
+ mode: null,
1895
+ provider: null
1896
+ };
1897
+ const emit = () => {
1898
+ for (const listener of listeners) {
1899
+ listener();
1900
+ }
1901
+ };
1902
+ const run = async (provider, mode) => {
1903
+ if (closed) {
1904
+ return snapshot.lastResult;
1905
+ }
1906
+ snapshot = {
1907
+ ...snapshot,
1908
+ error: null,
1909
+ isRunning: true,
1910
+ mode,
1911
+ provider
1912
+ };
1913
+ emit();
1914
+ try {
1915
+ const result = await postSimulation(pathPrefix, mode, provider, fetchImpl);
1916
+ snapshot = {
1917
+ error: null,
1918
+ isRunning: false,
1919
+ lastResult: result,
1920
+ mode,
1921
+ provider,
1922
+ updatedAt: Date.now()
1923
+ };
1924
+ emit();
1925
+ return result;
1926
+ } catch (error) {
1927
+ snapshot = {
1928
+ ...snapshot,
1929
+ error: error instanceof Error ? error.message : String(error),
1930
+ isRunning: false
1931
+ };
1932
+ emit();
1933
+ throw error;
1934
+ }
1935
+ };
1936
+ const close = () => {
1937
+ closed = true;
1938
+ listeners.clear();
1939
+ };
1940
+ return {
1941
+ close,
1942
+ getServerSnapshot: () => snapshot,
1943
+ getSnapshot: () => snapshot,
1944
+ run,
1945
+ subscribe: (listener) => {
1946
+ listeners.add(listener);
1947
+ return () => {
1948
+ listeners.delete(listener);
1949
+ };
1950
+ }
1951
+ };
1952
+ };
1953
+
1954
+ // src/client/providerSimulationControlsWidget.ts
1955
+ var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1956
+ var formatKind = (kind) => (kind ?? "stt").toUpperCase();
1957
+ var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
1958
+ const configuredProviders = options.providers.filter((provider) => provider.configured !== false);
1959
+ const fallbackReady = !options.fallbackRequiredProvider || configuredProviders.some((entry) => entry.provider === options.fallbackRequiredProvider);
1960
+ const failureProviders = (options.failureProviders ? options.failureProviders.map((provider) => ({ provider })) : configuredProviders).filter((provider) => configuredProviders.some((entry) => entry.provider === provider.provider));
1961
+ return {
1962
+ canSimulateFailure: configuredProviders.length > 0 && fallbackReady,
1963
+ description: options.failureMessage ?? `Simulate ${formatKind(options.kind)} provider failure and recovery without changing credentials.`,
1964
+ error: snapshot.error,
1965
+ failureProviders,
1966
+ isRunning: snapshot.isRunning,
1967
+ label: snapshot.isRunning ? `Running ${snapshot.mode ?? "simulation"}` : snapshot.lastResult ? `${snapshot.lastResult.provider} ${snapshot.lastResult.mode} simulated` : configuredProviders.length ? `${configuredProviders.length} configured` : "No configured providers",
1968
+ providers: configuredProviders,
1969
+ resultText: snapshot.lastResult ? JSON.stringify(snapshot.lastResult, null, 2) : null,
1970
+ title: options.title ?? `${formatKind(options.kind)} Failure Simulation`
1971
+ };
1972
+ };
1973
+ var renderVoiceProviderSimulationControlsHTML = (snapshot, options) => {
1974
+ const model = createVoiceProviderSimulationControlsViewModel(snapshot, options);
1975
+ const failureButtons = model.failureProviders.map((provider) => `<button type="button" data-voice-provider-fail="${escapeHtml6(provider.provider)}"${!model.canSimulateFailure || snapshot.isRunning ? " disabled" : ""}>Simulate ${escapeHtml6(provider.provider)} ${escapeHtml6(formatKind(options.kind))} failure</button>`).join("");
1976
+ const recoveryButtons = model.providers.map((provider) => `<button type="button" data-voice-provider-recover="${escapeHtml6(provider.provider)}"${snapshot.isRunning ? " disabled" : ""}>Mark ${escapeHtml6(provider.provider)} recovered</button>`).join("");
1977
+ return `<section class="absolute-voice-provider-simulation absolute-voice-provider-simulation--${snapshot.error ? "error" : snapshot.isRunning ? "running" : "ready"}">
1978
+ <header class="absolute-voice-provider-simulation__header">
1979
+ <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml6(model.title)}</span>
1980
+ <strong class="absolute-voice-provider-simulation__label">${escapeHtml6(model.label)}</strong>
1981
+ </header>
1982
+ <p class="absolute-voice-provider-simulation__description">${escapeHtml6(model.description)}</p>
1983
+ ${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml6(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
1984
+ <div class="absolute-voice-provider-simulation__actions">${failureButtons}${recoveryButtons}</div>
1985
+ ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml6(snapshot.error)}</p>` : ""}
1986
+ ${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml6(model.resultText)}</pre>` : ""}
1987
+ </section>`;
1988
+ };
1989
+ var bindVoiceProviderSimulationControls = (element, store) => {
1990
+ const onClick = (event) => {
1991
+ const target = event.target;
1992
+ if (!(target instanceof HTMLElement)) {
1993
+ return;
1994
+ }
1995
+ const failProvider = target.getAttribute("data-voice-provider-fail");
1996
+ const recoverProvider = target.getAttribute("data-voice-provider-recover");
1997
+ if (failProvider) {
1998
+ store.run(failProvider, "failure").catch(() => {});
1999
+ }
2000
+ if (recoverProvider) {
2001
+ store.run(recoverProvider, "recovery").catch(() => {});
2002
+ }
2003
+ };
2004
+ element.addEventListener("click", onClick);
2005
+ return () => element.removeEventListener("click", onClick);
2006
+ };
2007
+ var mountVoiceProviderSimulationControls = (element, options) => {
2008
+ const store = createVoiceProviderSimulationControlsStore(options);
2009
+ const render = () => {
2010
+ element.innerHTML = renderVoiceProviderSimulationControlsHTML(store.getSnapshot(), options);
2011
+ };
2012
+ const unsubscribeStore = store.subscribe(render);
2013
+ const unsubscribeDom = bindVoiceProviderSimulationControls(element, store);
2014
+ render();
2015
+ return {
2016
+ close: () => {
2017
+ unsubscribeDom();
2018
+ unsubscribeStore();
2019
+ store.close();
2020
+ },
2021
+ run: store.run
2022
+ };
2023
+ };
2024
+ var defineVoiceProviderSimulationControlsElement = (tagName = "absolute-voice-provider-simulation") => {
2025
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
2026
+ return;
2027
+ }
2028
+ customElements.define(tagName, class AbsoluteVoiceProviderSimulationElement extends HTMLElement {
2029
+ mounted;
2030
+ connectedCallback() {
2031
+ const providers = (this.getAttribute("providers") ?? "").split(",").map((provider) => provider.trim()).filter(Boolean).map((provider) => ({ provider }));
2032
+ const failureProviders = (this.getAttribute("failure-providers") ?? "").split(",").map((provider) => provider.trim()).filter(Boolean);
2033
+ this.mounted = mountVoiceProviderSimulationControls(this, {
2034
+ failureProviders: failureProviders.length ? failureProviders : undefined,
2035
+ fallbackRequiredMessage: this.getAttribute("fallback-required-message") ?? undefined,
2036
+ fallbackRequiredProvider: this.getAttribute("fallback-required-provider") ?? undefined,
2037
+ failureMessage: this.getAttribute("failure-message") ?? undefined,
2038
+ kind: this.getAttribute("kind") ?? "stt",
2039
+ pathPrefix: this.getAttribute("path-prefix") ?? undefined,
2040
+ providers,
2041
+ title: this.getAttribute("title") ?? undefined
2042
+ });
2043
+ }
2044
+ disconnectedCallback() {
2045
+ this.mounted?.close();
2046
+ this.mounted = undefined;
2047
+ }
2048
+ });
2049
+ };
2050
+
2051
+ // src/vue/useVoiceProviderSimulationControls.ts
2052
+ import { onUnmounted as onUnmounted6, ref as ref6 } from "vue";
2053
+ function useVoiceProviderSimulationControls(options) {
2054
+ const store = createVoiceProviderSimulationControlsStore(options);
2055
+ const error = ref6(null);
2056
+ const isRunning = ref6(false);
2057
+ const lastResult = ref6(null);
2058
+ const mode = ref6(null);
2059
+ const provider = ref6(null);
2060
+ const updatedAt = ref6(undefined);
2061
+ const sync = () => {
2062
+ const snapshot = store.getSnapshot();
2063
+ error.value = snapshot.error;
2064
+ isRunning.value = snapshot.isRunning;
2065
+ lastResult.value = snapshot.lastResult;
2066
+ mode.value = snapshot.mode;
2067
+ provider.value = snapshot.provider;
2068
+ updatedAt.value = snapshot.updatedAt;
2069
+ };
2070
+ const unsubscribe = store.subscribe(sync);
2071
+ sync();
2072
+ onUnmounted6(() => {
2073
+ unsubscribe();
2074
+ store.close();
2075
+ });
2076
+ return {
2077
+ error,
2078
+ isRunning,
2079
+ lastResult,
2080
+ mode,
2081
+ provider,
2082
+ run: store.run,
2083
+ updatedAt
2084
+ };
2085
+ }
2086
+
2087
+ // src/vue/VoiceProviderSimulationControls.ts
2088
+ var VoiceProviderSimulationControls = defineComponent6({
2089
+ name: "VoiceProviderSimulationControls",
2090
+ props: {
2091
+ class: { default: "", type: String },
2092
+ fallbackRequiredMessage: { default: undefined, type: String },
2093
+ fallbackRequiredProvider: { default: undefined, type: String },
2094
+ failureMessage: { default: undefined, type: String },
2095
+ failureProviders: {
2096
+ default: undefined,
2097
+ type: Array
2098
+ },
2099
+ kind: { default: "stt", type: String },
2100
+ pathPrefix: { default: undefined, type: String },
2101
+ providers: {
2102
+ required: true,
2103
+ type: Array
2104
+ },
2105
+ title: { default: undefined, type: String }
2106
+ },
2107
+ setup(props) {
2108
+ const options = {
2109
+ fallbackRequiredMessage: props.fallbackRequiredMessage,
2110
+ fallbackRequiredProvider: props.fallbackRequiredProvider,
2111
+ failureMessage: props.failureMessage,
2112
+ failureProviders: props.failureProviders,
2113
+ kind: props.kind,
2114
+ pathPrefix: props.pathPrefix,
2115
+ providers: props.providers,
2116
+ title: props.title
2117
+ };
2118
+ const controls = useVoiceProviderSimulationControls(options);
2119
+ const model = computed(() => createVoiceProviderSimulationControlsViewModel({
2120
+ error: controls.error.value,
2121
+ isRunning: controls.isRunning.value,
2122
+ lastResult: controls.lastResult.value,
2123
+ mode: controls.mode.value,
2124
+ provider: controls.provider.value,
2125
+ updatedAt: controls.updatedAt.value
2126
+ }, options));
2127
+ const run = (provider, mode) => {
2128
+ controls.run(provider, mode).catch(() => {});
2129
+ };
2130
+ return () => h6("section", {
2131
+ class: [
2132
+ "absolute-voice-provider-simulation",
2133
+ `absolute-voice-provider-simulation--${controls.error.value ? "error" : controls.isRunning.value ? "running" : "ready"}`,
2134
+ props.class
2135
+ ]
2136
+ }, [
2137
+ h6("header", { class: "absolute-voice-provider-simulation__header" }, [
2138
+ h6("span", { class: "absolute-voice-provider-simulation__eyebrow" }, model.value.title),
2139
+ h6("strong", { class: "absolute-voice-provider-simulation__label" }, model.value.label)
2140
+ ]),
2141
+ h6("p", { class: "absolute-voice-provider-simulation__description" }, model.value.description),
2142
+ model.value.canSimulateFailure ? null : h6("p", { class: "absolute-voice-provider-simulation__empty" }, props.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure."),
2143
+ h6("div", { class: "absolute-voice-provider-simulation__actions" }, [
2144
+ ...model.value.failureProviders.map((provider) => h6("button", {
2145
+ disabled: !model.value.canSimulateFailure || controls.isRunning.value,
2146
+ key: `fail-${provider.provider}`,
2147
+ onClick: () => run(provider.provider, "failure"),
2148
+ type: "button"
2149
+ }, `Simulate ${provider.provider} ${props.kind.toUpperCase()} failure`)),
2150
+ ...model.value.providers.map((provider) => h6("button", {
2151
+ disabled: controls.isRunning.value,
2152
+ key: `recover-${provider.provider}`,
2153
+ onClick: () => run(provider.provider, "recovery"),
2154
+ type: "button"
2155
+ }, `Mark ${provider.provider} recovered`))
2156
+ ]),
2157
+ controls.error.value ? h6("p", { class: "absolute-voice-provider-simulation__error" }, controls.error.value) : null,
2158
+ model.value.resultText ? h6("pre", { class: "absolute-voice-provider-simulation__result" }, model.value.resultText) : null
2159
+ ]);
2160
+ }
2161
+ });
2162
+ // src/vue/VoiceProviderCapabilities.ts
2163
+ import { computed as computed2, defineComponent as defineComponent7, h as h7 } from "vue";
2164
+
2165
+ // src/client/providerCapabilities.ts
2166
+ var fetchVoiceProviderCapabilities = async (path = "/api/provider-capabilities", options = {}) => {
2167
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2168
+ const response = await fetchImpl(path);
2169
+ if (!response.ok) {
2170
+ throw new Error(`Voice provider capabilities failed: HTTP ${response.status}`);
2171
+ }
2172
+ return await response.json();
2173
+ };
2174
+ var createVoiceProviderCapabilitiesStore = (path = "/api/provider-capabilities", options = {}) => {
2175
+ const listeners = new Set;
2176
+ let closed = false;
2177
+ let timer;
2178
+ let snapshot = {
2179
+ error: null,
2180
+ isLoading: false
2181
+ };
2182
+ const emit = () => {
2183
+ for (const listener of listeners) {
2184
+ listener();
2185
+ }
2186
+ };
2187
+ const refresh = async () => {
2188
+ if (closed) {
2189
+ return snapshot.report;
2190
+ }
2191
+ snapshot = {
2192
+ ...snapshot,
2193
+ error: null,
2194
+ isLoading: true
2195
+ };
2196
+ emit();
2197
+ try {
2198
+ const report = await fetchVoiceProviderCapabilities(path, options);
2199
+ snapshot = {
2200
+ error: null,
2201
+ isLoading: false,
2202
+ report,
2203
+ updatedAt: Date.now()
2204
+ };
2205
+ emit();
2206
+ return report;
2207
+ } catch (error) {
2208
+ snapshot = {
2209
+ ...snapshot,
2210
+ error: error instanceof Error ? error.message : String(error),
2211
+ isLoading: false
2212
+ };
2213
+ emit();
2214
+ throw error;
2215
+ }
2216
+ };
2217
+ const close = () => {
2218
+ closed = true;
2219
+ if (timer) {
2220
+ clearInterval(timer);
2221
+ timer = undefined;
2222
+ }
2223
+ listeners.clear();
2224
+ };
2225
+ if (options.intervalMs && options.intervalMs > 0) {
2226
+ timer = setInterval(() => {
2227
+ refresh().catch(() => {});
2228
+ }, options.intervalMs);
2229
+ }
2230
+ return {
2231
+ close,
2232
+ getServerSnapshot: () => snapshot,
2233
+ getSnapshot: () => snapshot,
2234
+ refresh,
2235
+ subscribe: (listener) => {
2236
+ listeners.add(listener);
2237
+ return () => {
2238
+ listeners.delete(listener);
2239
+ };
2240
+ }
2241
+ };
2242
+ };
2243
+
2244
+ // src/client/providerCapabilitiesWidget.ts
2245
+ var DEFAULT_TITLE6 = "Provider Capabilities";
2246
+ var DEFAULT_DESCRIPTION6 = "Configured, selected, and healthy voice providers for this deployment.";
2247
+ var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2248
+ var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
2249
+ var formatKind2 = (kind) => kind.toUpperCase();
2250
+ var formatStatus2 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
2251
+ var getCapabilityDetail = (capability) => {
2252
+ if (!capability.configured) {
2253
+ return "Not configured in this deployment.";
2254
+ }
2255
+ if (capability.selected) {
2256
+ return `Selected ${capability.kind.toUpperCase()} provider for new sessions.`;
2257
+ }
2258
+ if (capability.health?.status === "healthy") {
2259
+ return "Configured and healthy fallback candidate.";
2260
+ }
2261
+ if (capability.health?.status === "idle") {
2262
+ return "Configured; no traffic observed yet.";
2263
+ }
2264
+ if (capability.health?.lastError) {
2265
+ return capability.health.lastError;
2266
+ }
2267
+ return "Configured and available.";
2268
+ };
2269
+ var isWarningStatus = (status) => status === "degraded" || status === "rate-limited" || status === "suppressed" || status === "unconfigured";
2270
+ var createVoiceProviderCapabilitiesViewModel = (snapshot, options = {}) => {
2271
+ const capabilities = (snapshot.report?.capabilities ?? []).map((capability) => ({
2272
+ ...capability,
2273
+ detail: getCapabilityDetail(capability),
2274
+ label: `${formatProvider(capability.provider)} ${formatKind2(capability.kind)}`,
2275
+ rows: [
2276
+ { label: "Status", value: formatStatus2(capability.status) },
2277
+ { label: "Selected", value: capability.selected ? "Yes" : "No" },
2278
+ { label: "Model", value: capability.model ?? "Default" },
2279
+ {
2280
+ label: "Features",
2281
+ value: capability.features?.join(", ") || "Not specified"
2282
+ },
2283
+ { label: "Runs", value: String(capability.health?.runCount ?? 0) },
2284
+ { label: "Errors", value: String(capability.health?.errorCount ?? 0) }
2285
+ ]
2286
+ }));
2287
+ const warningCount = capabilities.filter((capability) => isWarningStatus(capability.status)).length;
2288
+ const selectedCount = snapshot.report?.selected ?? capabilities.filter((capability) => capability.selected).length;
2289
+ return {
2290
+ capabilities,
2291
+ description: options.description ?? DEFAULT_DESCRIPTION6,
2292
+ error: snapshot.error,
2293
+ isLoading: snapshot.isLoading,
2294
+ label: snapshot.error ? "Unavailable" : capabilities.length ? warningCount > 0 ? `${warningCount} needs attention` : `${selectedCount} selected` : snapshot.isLoading ? "Checking" : "No capabilities",
2295
+ status: snapshot.error ? "error" : capabilities.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
2296
+ title: options.title ?? DEFAULT_TITLE6,
2297
+ updatedAt: snapshot.updatedAt
2298
+ };
2299
+ };
2300
+ var renderVoiceProviderCapabilitiesHTML = (snapshot, options = {}) => {
2301
+ const model = createVoiceProviderCapabilitiesViewModel(snapshot, options);
2302
+ const capabilities = model.capabilities.length ? `<div class="absolute-voice-provider-capabilities__providers">${model.capabilities.map((capability) => `<article class="absolute-voice-provider-capabilities__provider absolute-voice-provider-capabilities__provider--${escapeHtml7(capability.status)}">
2303
+ <header>
2304
+ <strong>${escapeHtml7(capability.label)}</strong>
2305
+ <span>${escapeHtml7(formatStatus2(capability.status))}</span>
2306
+ </header>
2307
+ <p>${escapeHtml7(capability.detail)}</p>
2308
+ <dl>${capability.rows.map((row) => `<div>
2309
+ <dt>${escapeHtml7(row.label)}</dt>
2310
+ <dd>${escapeHtml7(row.value)}</dd>
2311
+ </div>`).join("")}</dl>
2312
+ </article>`).join("")}</div>` : '<p class="absolute-voice-provider-capabilities__empty">Configure provider capabilities to see deployment coverage.</p>';
2313
+ return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml7(model.status)}">
2314
+ <header class="absolute-voice-provider-capabilities__header">
2315
+ <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml7(model.title)}</span>
2316
+ <strong class="absolute-voice-provider-capabilities__label">${escapeHtml7(model.label)}</strong>
2317
+ </header>
2318
+ <p class="absolute-voice-provider-capabilities__description">${escapeHtml7(model.description)}</p>
2319
+ ${capabilities}
2320
+ ${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml7(model.error)}</p>` : ""}
2321
+ </section>`;
2322
+ };
2323
+ 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}`;
2324
+ var mountVoiceProviderCapabilities = (element, path = "/api/provider-capabilities", options = {}) => {
2325
+ const store = createVoiceProviderCapabilitiesStore(path, options);
2326
+ const render = () => {
2327
+ element.innerHTML = renderVoiceProviderCapabilitiesHTML(store.getSnapshot(), options);
2328
+ };
2329
+ const unsubscribe = store.subscribe(render);
2330
+ render();
2331
+ store.refresh().catch(() => {});
2332
+ return {
2333
+ close: () => {
2334
+ unsubscribe();
2335
+ store.close();
2336
+ },
2337
+ refresh: store.refresh
2338
+ };
2339
+ };
2340
+ var defineVoiceProviderCapabilitiesElement = (tagName = "absolute-voice-provider-capabilities") => {
2341
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
2342
+ return;
2343
+ }
2344
+ customElements.define(tagName, class AbsoluteVoiceProviderCapabilitiesElement extends HTMLElement {
2345
+ mounted;
2346
+ connectedCallback() {
2347
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
2348
+ this.mounted = mountVoiceProviderCapabilities(this, this.getAttribute("path") ?? "/api/provider-capabilities", {
2349
+ description: this.getAttribute("description") ?? undefined,
2350
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
2351
+ title: this.getAttribute("title") ?? undefined
2352
+ });
2353
+ }
2354
+ disconnectedCallback() {
2355
+ this.mounted?.close();
2356
+ this.mounted = undefined;
2357
+ }
2358
+ });
2359
+ };
2360
+
2361
+ // src/vue/useVoiceProviderCapabilities.ts
2362
+ import { onUnmounted as onUnmounted7, shallowRef as shallowRef6 } from "vue";
2363
+ function useVoiceProviderCapabilities(path = "/api/provider-capabilities", options = {}) {
2364
+ const store = createVoiceProviderCapabilitiesStore(path, options);
2365
+ const error = shallowRef6(null);
2366
+ const isLoading = shallowRef6(false);
2367
+ const report = shallowRef6();
2368
+ const updatedAt = shallowRef6(undefined);
2369
+ const sync = () => {
2370
+ const snapshot = store.getSnapshot();
2371
+ error.value = snapshot.error;
2372
+ isLoading.value = snapshot.isLoading;
2373
+ report.value = snapshot.report;
2374
+ updatedAt.value = snapshot.updatedAt;
2375
+ };
2376
+ const unsubscribe = store.subscribe(sync);
2377
+ sync();
2378
+ store.refresh().catch(() => {});
2379
+ onUnmounted7(() => {
2380
+ unsubscribe();
2381
+ store.close();
2382
+ });
2383
+ return {
2384
+ error,
2385
+ isLoading,
2386
+ refresh: store.refresh,
2387
+ report,
2388
+ updatedAt
2389
+ };
2390
+ }
2391
+
2392
+ // src/vue/VoiceProviderCapabilities.ts
2393
+ var VoiceProviderCapabilities = defineComponent7({
2394
+ name: "VoiceProviderCapabilities",
2395
+ props: {
2396
+ class: {
2397
+ default: "",
2398
+ type: String
2399
+ },
2400
+ description: {
2401
+ default: undefined,
2402
+ type: String
2403
+ },
2404
+ intervalMs: {
2405
+ default: 5000,
2406
+ type: Number
2407
+ },
2408
+ path: {
2409
+ default: "/api/provider-capabilities",
2410
+ type: String
2411
+ },
2412
+ title: {
2413
+ default: undefined,
2414
+ type: String
2415
+ }
2416
+ },
2417
+ setup(props) {
2418
+ const options = {
2419
+ description: props.description,
2420
+ intervalMs: props.intervalMs,
2421
+ title: props.title
2422
+ };
2423
+ const capabilities = useVoiceProviderCapabilities(props.path, options);
2424
+ const model = computed2(() => createVoiceProviderCapabilitiesViewModel({
2425
+ error: capabilities.error.value,
2426
+ isLoading: capabilities.isLoading.value,
2427
+ report: capabilities.report.value,
2428
+ updatedAt: capabilities.updatedAt.value
2429
+ }, options));
2430
+ return () => h7("section", {
2431
+ class: [
2432
+ "absolute-voice-provider-capabilities",
2433
+ `absolute-voice-provider-capabilities--${model.value.status}`,
2434
+ props.class
2435
+ ]
2436
+ }, [
2437
+ h7("header", { class: "absolute-voice-provider-capabilities__header" }, [
2438
+ h7("span", { class: "absolute-voice-provider-capabilities__eyebrow" }, model.value.title),
2439
+ h7("strong", { class: "absolute-voice-provider-capabilities__label" }, model.value.label)
2440
+ ]),
2441
+ h7("p", { class: "absolute-voice-provider-capabilities__description" }, model.value.description),
2442
+ model.value.capabilities.length ? h7("div", { class: "absolute-voice-provider-capabilities__providers" }, model.value.capabilities.map((capability) => h7("article", {
2443
+ class: [
2444
+ "absolute-voice-provider-capabilities__provider",
2445
+ `absolute-voice-provider-capabilities__provider--${capability.status}`
2446
+ ],
2447
+ key: `${capability.kind}:${capability.provider}`
2448
+ }, [
2449
+ h7("header", [
2450
+ h7("strong", capability.label),
2451
+ h7("span", capability.status)
2452
+ ]),
2453
+ h7("p", capability.detail),
2454
+ h7("dl", capability.rows.map((row) => h7("div", { key: row.label }, [
2455
+ h7("dt", row.label),
2456
+ h7("dd", row.value)
2457
+ ])))
2458
+ ]))) : h7("p", { class: "absolute-voice-provider-capabilities__empty" }, "Configure provider capabilities to see deployment coverage."),
2459
+ model.value.error ? h7("p", { class: "absolute-voice-provider-capabilities__error" }, model.value.error) : null
2460
+ ]);
2461
+ }
2462
+ });
2463
+ // src/vue/VoiceProviderContracts.ts
2464
+ import { defineComponent as defineComponent8, h as h8 } from "vue";
2465
+
2466
+ // src/vue/useVoiceProviderContracts.ts
2467
+ import { onUnmounted as onUnmounted8, shallowRef as shallowRef7 } from "vue";
2468
+
2469
+ // src/client/providerContracts.ts
2470
+ var fetchVoiceProviderContracts = async (path = "/api/provider-contracts", options = {}) => {
2471
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2472
+ const response = await fetchImpl(path);
2473
+ if (!response.ok) {
2474
+ throw new Error(`Voice provider contracts failed: HTTP ${response.status}`);
2475
+ }
2476
+ return await response.json();
2477
+ };
2478
+ var createVoiceProviderContractsStore = (path = "/api/provider-contracts", options = {}) => {
2479
+ const listeners = new Set;
2480
+ let closed = false;
2481
+ let timer;
2482
+ let snapshot = {
2483
+ error: null,
2484
+ isLoading: false
2485
+ };
2486
+ const emit = () => {
2487
+ for (const listener of listeners) {
2488
+ listener();
2489
+ }
2490
+ };
2491
+ const refresh = async () => {
2492
+ if (closed) {
2493
+ return snapshot.report;
2494
+ }
2495
+ snapshot = { ...snapshot, error: null, isLoading: true };
2496
+ emit();
2497
+ try {
2498
+ const report = await fetchVoiceProviderContracts(path, options);
2499
+ snapshot = {
2500
+ error: null,
2501
+ isLoading: false,
2502
+ report,
2503
+ updatedAt: Date.now()
2504
+ };
2505
+ emit();
2506
+ return report;
2507
+ } catch (error) {
2508
+ snapshot = {
2509
+ ...snapshot,
2510
+ error: error instanceof Error ? error.message : String(error),
2511
+ isLoading: false
2512
+ };
2513
+ emit();
2514
+ throw error;
2515
+ }
2516
+ };
2517
+ const close = () => {
2518
+ closed = true;
2519
+ if (timer) {
2520
+ clearInterval(timer);
2521
+ timer = undefined;
2522
+ }
2523
+ listeners.clear();
2524
+ };
2525
+ if (options.intervalMs && options.intervalMs > 0) {
2526
+ timer = setInterval(() => {
2527
+ refresh().catch(() => {});
2528
+ }, options.intervalMs);
2529
+ }
2530
+ return {
2531
+ close,
2532
+ getServerSnapshot: () => snapshot,
2533
+ getSnapshot: () => snapshot,
2534
+ refresh,
2535
+ subscribe: (listener) => {
2536
+ listeners.add(listener);
2537
+ return () => {
2538
+ listeners.delete(listener);
2539
+ };
2540
+ }
2541
+ };
2542
+ };
2543
+
2544
+ // src/vue/useVoiceProviderContracts.ts
2545
+ function useVoiceProviderContracts(path = "/api/provider-contracts", options = {}) {
2546
+ const store = createVoiceProviderContractsStore(path, options);
2547
+ const error = shallowRef7(null);
2548
+ const isLoading = shallowRef7(false);
2549
+ const report = shallowRef7();
2550
+ const updatedAt = shallowRef7(undefined);
2551
+ const sync = () => {
2552
+ const snapshot = store.getSnapshot();
2553
+ error.value = snapshot.error;
2554
+ isLoading.value = snapshot.isLoading;
2555
+ report.value = snapshot.report;
2556
+ updatedAt.value = snapshot.updatedAt;
2557
+ };
2558
+ const unsubscribe = store.subscribe(sync);
2559
+ sync();
2560
+ store.refresh().catch(() => {});
2561
+ onUnmounted8(() => {
2562
+ unsubscribe();
2563
+ store.close();
2564
+ });
2565
+ return {
2566
+ error,
2567
+ isLoading,
2568
+ refresh: store.refresh,
2569
+ report,
2570
+ updatedAt
2571
+ };
2572
+ }
2573
+
2574
+ // src/client/providerContractsWidget.ts
2575
+ var DEFAULT_TITLE7 = "Provider Contracts";
2576
+ var DEFAULT_DESCRIPTION7 = "Production contract coverage for provider env, latency, fallback, streaming, and capabilities.";
2577
+ var escapeHtml8 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2578
+ var formatProvider2 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
2579
+ var formatStatus3 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
2580
+ var contractDetail = (row) => {
2581
+ const failing = row.checks.filter((check) => check.status !== "pass");
2582
+ if (failing.length === 0) {
2583
+ return "Provider contract is production-ready.";
2584
+ }
2585
+ return failing.map((check) => `${check.label}: ${check.detail ?? check.status}`).join(" ");
2586
+ };
2587
+ var createVoiceProviderContractsViewModel = (snapshot, options = {}) => {
2588
+ const rows = (snapshot.report?.rows ?? []).map((row) => ({
2589
+ ...row,
2590
+ detail: contractDetail(row),
2591
+ label: `${formatProvider2(row.provider)} ${row.kind.toUpperCase()}`,
2592
+ remediations: row.checks.filter((check) => check.status !== "pass" && check.remediation).map((check) => ({
2593
+ detail: check.remediation?.detail ?? "",
2594
+ href: check.remediation?.href,
2595
+ label: check.remediation?.label ?? check.label
2596
+ })),
2597
+ rows: [
2598
+ { label: "Status", value: formatStatus3(row.status) },
2599
+ { label: "Selected", value: row.selected ? "Yes" : "No" },
2600
+ { label: "Configured", value: row.configured ? "Yes" : "No" },
2601
+ {
2602
+ label: "Checks",
2603
+ value: row.checks.map((check) => `${check.label}: ${formatStatus3(check.status)}`).join(", ")
2604
+ }
2605
+ ]
2606
+ }));
2607
+ const warningCount = snapshot.report ? snapshot.report.failed + snapshot.report.warned : rows.filter((row) => row.status !== "pass").length;
2608
+ return {
2609
+ description: options.description ?? DEFAULT_DESCRIPTION7,
2610
+ error: snapshot.error,
2611
+ isLoading: snapshot.isLoading,
2612
+ label: snapshot.error ? "Unavailable" : rows.length ? warningCount > 0 ? `${warningCount} needs attention` : `${rows.length} passing` : snapshot.isLoading ? "Checking" : "No contracts",
2613
+ rows,
2614
+ status: snapshot.error ? "error" : rows.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
2615
+ title: options.title ?? DEFAULT_TITLE7,
2616
+ updatedAt: snapshot.updatedAt
2617
+ };
2618
+ };
2619
+ var renderVoiceProviderContractsHTML = (snapshot, options = {}) => {
2620
+ const model = createVoiceProviderContractsViewModel(snapshot, options);
2621
+ 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--${escapeHtml8(row.status)}">
2622
+ <header>
2623
+ <strong>${escapeHtml8(row.label)}</strong>
2624
+ <span>${escapeHtml8(formatStatus3(row.status))}</span>
2625
+ </header>
2626
+ <p>${escapeHtml8(row.detail)}</p>
2627
+ ${row.remediations.length ? `<ul class="absolute-voice-provider-contracts__remediations">${row.remediations.map((remediation) => `<li>${remediation.href ? `<a href="${escapeHtml8(remediation.href)}">${escapeHtml8(remediation.label)}</a>` : `<strong>${escapeHtml8(remediation.label)}</strong>`}<span>${escapeHtml8(remediation.detail)}</span></li>`).join("")}</ul>` : ""}
2628
+ <dl>${row.rows.map((item) => `<div>
2629
+ <dt>${escapeHtml8(item.label)}</dt>
2630
+ <dd>${escapeHtml8(item.value)}</dd>
2631
+ </div>`).join("")}</dl>
2632
+ </article>`).join("")}</div>` : '<p class="absolute-voice-provider-contracts__empty">Configure provider contracts to see production coverage.</p>';
2633
+ return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${escapeHtml8(model.status)}">
2634
+ <header class="absolute-voice-provider-contracts__header">
2635
+ <span class="absolute-voice-provider-contracts__eyebrow">${escapeHtml8(model.title)}</span>
2636
+ <strong class="absolute-voice-provider-contracts__label">${escapeHtml8(model.label)}</strong>
2637
+ </header>
2638
+ <p class="absolute-voice-provider-contracts__description">${escapeHtml8(model.description)}</p>
2639
+ ${rows}
2640
+ ${model.error ? `<p class="absolute-voice-provider-contracts__error">${escapeHtml8(model.error)}</p>` : ""}
2641
+ </section>`;
2642
+ };
2643
+ 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}`;
2644
+ var mountVoiceProviderContracts = (element, path = "/api/provider-contracts", options = {}) => {
2645
+ const store = createVoiceProviderContractsStore(path, options);
2646
+ const render = () => {
2647
+ element.innerHTML = renderVoiceProviderContractsHTML(store.getSnapshot(), options);
2648
+ };
2649
+ const unsubscribe = store.subscribe(render);
2650
+ render();
2651
+ store.refresh().catch(() => {});
2652
+ return {
2653
+ close: () => {
2654
+ unsubscribe();
2655
+ store.close();
2656
+ },
2657
+ refresh: store.refresh
2658
+ };
2659
+ };
2660
+ var defineVoiceProviderContractsElement = (tagName = "absolute-voice-provider-contracts") => {
2661
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
2662
+ return;
2663
+ }
2664
+ customElements.define(tagName, class AbsoluteVoiceProviderContractsElement extends HTMLElement {
2665
+ mounted;
2666
+ connectedCallback() {
2667
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
2668
+ this.mounted = mountVoiceProviderContracts(this, this.getAttribute("path") ?? "/api/provider-contracts", {
2669
+ description: this.getAttribute("description") ?? undefined,
2670
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
2671
+ title: this.getAttribute("title") ?? undefined
2672
+ });
2673
+ }
2674
+ disconnectedCallback() {
2675
+ this.mounted?.close();
2676
+ this.mounted = undefined;
2677
+ }
2678
+ });
2679
+ };
2680
+
2681
+ // src/vue/VoiceProviderContracts.ts
2682
+ var VoiceProviderContracts = defineComponent8({
2683
+ name: "VoiceProviderContracts",
2684
+ props: {
2685
+ description: String,
2686
+ intervalMs: Number,
2687
+ path: {
2688
+ default: "/api/provider-contracts",
2689
+ type: String
2690
+ },
2691
+ title: String
2692
+ },
2693
+ setup(props) {
2694
+ const state = useVoiceProviderContracts(props.path, {
2695
+ description: props.description,
2696
+ intervalMs: props.intervalMs,
2697
+ title: props.title
2698
+ });
2699
+ return () => {
2700
+ const model = createVoiceProviderContractsViewModel({
2701
+ error: state.error.value,
2702
+ isLoading: state.isLoading.value,
2703
+ report: state.report.value,
2704
+ updatedAt: state.updatedAt.value
2705
+ }, {
2706
+ description: props.description,
2707
+ intervalMs: props.intervalMs,
2708
+ title: props.title
2709
+ });
2710
+ return h8("section", {
2711
+ class: [
2712
+ "absolute-voice-provider-contracts",
2713
+ `absolute-voice-provider-contracts--${model.status}`
2714
+ ]
2715
+ }, [
2716
+ h8("header", { class: "absolute-voice-provider-contracts__header" }, [
2717
+ h8("span", { class: "absolute-voice-provider-contracts__eyebrow" }, model.title),
2718
+ h8("strong", { class: "absolute-voice-provider-contracts__label" }, model.label)
2719
+ ]),
2720
+ h8("p", { class: "absolute-voice-provider-contracts__description" }, model.description),
2721
+ model.rows.length ? h8("div", { class: "absolute-voice-provider-contracts__rows" }, model.rows.map((row) => h8("article", {
2722
+ class: [
2723
+ "absolute-voice-provider-contracts__row",
2724
+ `absolute-voice-provider-contracts__row--${row.status}`
2725
+ ],
2726
+ key: `${row.kind}:${row.provider}`
2727
+ }, [
2728
+ h8("header", [
2729
+ h8("strong", row.label),
2730
+ h8("span", row.status)
2731
+ ]),
2732
+ h8("p", row.detail),
2733
+ row.remediations.length ? h8("ul", {
2734
+ class: "absolute-voice-provider-contracts__remediations"
2735
+ }, row.remediations.map((remediation) => h8("li", {
2736
+ key: `${row.kind}:${row.provider}:${remediation.label}`
2737
+ }, [
2738
+ remediation.href ? h8("a", { href: remediation.href }, remediation.label) : h8("strong", remediation.label),
2739
+ h8("span", remediation.detail)
2740
+ ]))) : null,
2741
+ h8("dl", row.rows.map((item) => h8("div", { key: item.label }, [
2742
+ h8("dt", item.label),
2743
+ h8("dd", item.value)
2744
+ ])))
2745
+ ]))) : h8("p", { class: "absolute-voice-provider-contracts__empty" }, "Configure provider contracts to see production coverage."),
2746
+ model.error ? h8("p", { class: "absolute-voice-provider-contracts__error" }, model.error) : null
2747
+ ]);
2748
+ };
2749
+ }
2750
+ });
2751
+ // src/vue/VoiceProviderStatus.ts
2752
+ import { computed as computed3, defineComponent as defineComponent9, h as h9 } from "vue";
2753
+
2754
+ // src/client/providerStatus.ts
2755
+ var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
2756
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2757
+ const response = await fetchImpl(path);
2758
+ if (!response.ok) {
2759
+ throw new Error(`Voice provider status failed: HTTP ${response.status}`);
2760
+ }
2761
+ return await response.json();
2762
+ };
2763
+ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
2764
+ const listeners = new Set;
2765
+ let closed = false;
2766
+ let timer;
2767
+ let snapshot = {
2768
+ error: null,
2769
+ isLoading: false,
2770
+ providers: []
2771
+ };
2772
+ const emit = () => {
2773
+ for (const listener of listeners) {
2774
+ listener();
2775
+ }
2776
+ };
2777
+ const refresh = async () => {
2778
+ if (closed) {
2779
+ return snapshot.providers;
2780
+ }
2781
+ snapshot = {
2782
+ ...snapshot,
2783
+ error: null,
2784
+ isLoading: true
2785
+ };
2786
+ emit();
2787
+ try {
2788
+ const providers = await fetchVoiceProviderStatus(path, options);
2789
+ snapshot = {
2790
+ error: null,
2791
+ isLoading: false,
2792
+ providers,
2793
+ updatedAt: Date.now()
2794
+ };
2795
+ emit();
2796
+ return providers;
2797
+ } catch (error) {
2798
+ snapshot = {
2799
+ ...snapshot,
2800
+ error: error instanceof Error ? error.message : String(error),
2801
+ isLoading: false
2802
+ };
2803
+ emit();
2804
+ throw error;
2805
+ }
2806
+ };
2807
+ const close = () => {
2808
+ closed = true;
2809
+ if (timer) {
2810
+ clearInterval(timer);
2811
+ timer = undefined;
2812
+ }
2813
+ listeners.clear();
2814
+ };
2815
+ if (options.intervalMs && options.intervalMs > 0) {
2816
+ timer = setInterval(() => {
2817
+ refresh().catch(() => {});
2818
+ }, options.intervalMs);
2819
+ }
2820
+ return {
2821
+ close,
2822
+ getServerSnapshot: () => snapshot,
2823
+ getSnapshot: () => snapshot,
2824
+ refresh,
2825
+ subscribe: (listener) => {
2826
+ listeners.add(listener);
2827
+ return () => {
2828
+ listeners.delete(listener);
2829
+ };
2830
+ }
2831
+ };
2832
+ };
2833
+
2834
+ // src/client/providerStatusWidget.ts
2835
+ var DEFAULT_TITLE8 = "Voice Providers";
2836
+ var DEFAULT_DESCRIPTION8 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
2837
+ var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2838
+ var formatProvider3 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
2839
+ var formatStatus4 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
2840
+ var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
2841
+ var formatSuppression = (value) => typeof value === "number" ? `${Math.ceil(value / 1000)}s` : "None";
2842
+ var getProviderDetail = (provider) => {
2843
+ if (provider.status === "suppressed") {
2844
+ return provider.lastError ? `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)} after ${provider.lastError}.` : `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)}.`;
2845
+ }
2846
+ if (provider.status === "recoverable") {
2847
+ return "Cooldown expired; ready for recovery traffic.";
2848
+ }
2849
+ if (provider.status === "rate-limited") {
2850
+ return "Rate limit detected; router should avoid this provider.";
2851
+ }
2852
+ if (provider.status === "degraded") {
2853
+ return provider.lastError ?? "Recent provider errors detected.";
2854
+ }
2855
+ if (provider.status === "healthy") {
2856
+ return provider.recommended ? "Healthy and currently recommended." : "Healthy and available for routing.";
2857
+ }
2858
+ return "No provider traffic observed yet.";
2859
+ };
2860
+ var isWarningStatus2 = (status) => status === "degraded" || status === "rate-limited" || status === "recoverable" || status === "suppressed";
2861
+ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
2862
+ const providers = snapshot.providers.map((provider) => ({
2863
+ ...provider,
2864
+ detail: getProviderDetail(provider),
2865
+ label: `${formatProvider3(provider.provider)}${provider.recommended ? " recommended" : ""}`,
2866
+ rows: [
2867
+ { label: "Runs", value: String(provider.runCount) },
2868
+ { label: "Avg latency", value: formatLatency(provider.averageElapsedMs) },
2869
+ { label: "Errors", value: String(provider.errorCount) },
2870
+ { label: "Timeouts", value: String(provider.timeoutCount) },
2871
+ { label: "Fallbacks", value: String(provider.fallbackCount) },
2872
+ {
2873
+ label: "Suppression",
2874
+ value: formatSuppression(provider.suppressionRemainingMs)
2875
+ }
2876
+ ]
2877
+ }));
2878
+ const warningCount = providers.filter((provider) => isWarningStatus2(provider.status)).length;
2879
+ const healthyCount = providers.filter((provider) => provider.status === "healthy").length;
2880
+ return {
2881
+ description: options.description ?? DEFAULT_DESCRIPTION8,
2882
+ error: snapshot.error,
2883
+ isLoading: snapshot.isLoading,
2884
+ label: snapshot.error ? "Unavailable" : providers.length ? warningCount > 0 ? `${warningCount} needs attention` : `${healthyCount} healthy` : snapshot.isLoading ? "Checking" : "No provider traffic",
2885
+ providers,
2886
+ status: snapshot.error ? "error" : providers.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
2887
+ title: options.title ?? DEFAULT_TITLE8,
2888
+ updatedAt: snapshot.updatedAt
2889
+ };
2890
+ };
2891
+ var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
2892
+ const model = createVoiceProviderStatusViewModel(snapshot, options);
2893
+ 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--${escapeHtml9(provider.status)}">
2894
+ <header>
2895
+ <strong>${escapeHtml9(provider.label)}</strong>
2896
+ <span>${escapeHtml9(formatStatus4(provider.status))}</span>
2897
+ </header>
2898
+ <p>${escapeHtml9(provider.detail)}</p>
2899
+ <dl>${provider.rows.map((row) => `<div>
2900
+ <dt>${escapeHtml9(row.label)}</dt>
2901
+ <dd>${escapeHtml9(row.value)}</dd>
2902
+ </div>`).join("")}</dl>
2903
+ </article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
2904
+ return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml9(model.status)}">
2905
+ <header class="absolute-voice-provider-status__header">
2906
+ <span class="absolute-voice-provider-status__eyebrow">${escapeHtml9(model.title)}</span>
2907
+ <strong class="absolute-voice-provider-status__label">${escapeHtml9(model.label)}</strong>
2908
+ </header>
2909
+ <p class="absolute-voice-provider-status__description">${escapeHtml9(model.description)}</p>
2910
+ ${providers}
2911
+ ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml9(model.error)}</p>` : ""}
2912
+ </section>`;
2913
+ };
2914
+ 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}`;
2915
+ var mountVoiceProviderStatus = (element, path = "/api/provider-status", options = {}) => {
2916
+ const store = createVoiceProviderStatusStore(path, options);
2917
+ const render = () => {
2918
+ element.innerHTML = renderVoiceProviderStatusHTML(store.getSnapshot(), options);
2919
+ };
2920
+ const unsubscribe = store.subscribe(render);
2921
+ render();
2922
+ store.refresh().catch(() => {});
2923
+ return {
2924
+ close: () => {
2925
+ unsubscribe();
2926
+ store.close();
2927
+ },
2928
+ refresh: store.refresh
2929
+ };
2930
+ };
2931
+ var defineVoiceProviderStatusElement = (tagName = "absolute-voice-provider-status") => {
2932
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
2933
+ return;
2934
+ }
2935
+ customElements.define(tagName, class AbsoluteVoiceProviderStatusElement extends HTMLElement {
2936
+ mounted;
2937
+ connectedCallback() {
2938
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
2939
+ this.mounted = mountVoiceProviderStatus(this, this.getAttribute("path") ?? "/api/provider-status", {
2940
+ description: this.getAttribute("description") ?? undefined,
2941
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
2942
+ title: this.getAttribute("title") ?? undefined
2943
+ });
2944
+ }
2945
+ disconnectedCallback() {
2946
+ this.mounted?.close();
2947
+ this.mounted = undefined;
2948
+ }
2949
+ });
2950
+ };
2951
+
2952
+ // src/vue/useVoiceProviderStatus.ts
2953
+ import { onUnmounted as onUnmounted9, ref as ref7, shallowRef as shallowRef8 } from "vue";
2954
+ function useVoiceProviderStatus(path = "/api/provider-status", options = {}) {
2955
+ const store = createVoiceProviderStatusStore(path, options);
2956
+ const error = ref7(null);
2957
+ const isLoading = ref7(false);
2958
+ const providers = shallowRef8([]);
2959
+ const updatedAt = ref7(undefined);
2960
+ const sync = () => {
2961
+ const snapshot = store.getSnapshot();
2962
+ error.value = snapshot.error;
2963
+ isLoading.value = snapshot.isLoading;
2964
+ providers.value = [...snapshot.providers];
2965
+ updatedAt.value = snapshot.updatedAt;
2966
+ };
2967
+ const unsubscribe = store.subscribe(sync);
2968
+ sync();
2969
+ store.refresh().catch(() => {});
2970
+ onUnmounted9(() => {
2971
+ unsubscribe();
2972
+ store.close();
2973
+ });
2974
+ return {
2975
+ error,
2976
+ isLoading,
2977
+ providers,
2978
+ refresh: store.refresh,
2979
+ updatedAt
2980
+ };
2981
+ }
2982
+
2983
+ // src/vue/VoiceProviderStatus.ts
2984
+ var VoiceProviderStatus = defineComponent9({
2985
+ name: "VoiceProviderStatus",
2986
+ props: {
2987
+ class: {
2988
+ default: "",
2989
+ type: String
2990
+ },
2991
+ description: {
2992
+ default: undefined,
2993
+ type: String
2994
+ },
2995
+ intervalMs: {
2996
+ default: 5000,
2997
+ type: Number
2998
+ },
2999
+ path: {
3000
+ default: "/api/provider-status",
3001
+ type: String
3002
+ },
3003
+ title: {
3004
+ default: undefined,
3005
+ type: String
3006
+ }
3007
+ },
3008
+ setup(props) {
3009
+ const options = {
3010
+ description: props.description,
3011
+ intervalMs: props.intervalMs,
3012
+ title: props.title
3013
+ };
3014
+ const status = useVoiceProviderStatus(props.path, options);
3015
+ const model = computed3(() => createVoiceProviderStatusViewModel({
3016
+ error: status.error.value,
3017
+ isLoading: status.isLoading.value,
3018
+ providers: status.providers.value,
3019
+ updatedAt: status.updatedAt.value
3020
+ }, options));
3021
+ return () => h9("section", {
3022
+ class: [
3023
+ "absolute-voice-provider-status",
3024
+ `absolute-voice-provider-status--${model.value.status}`,
3025
+ props.class
3026
+ ]
3027
+ }, [
3028
+ h9("header", { class: "absolute-voice-provider-status__header" }, [
3029
+ h9("span", { class: "absolute-voice-provider-status__eyebrow" }, model.value.title),
3030
+ h9("strong", { class: "absolute-voice-provider-status__label" }, model.value.label)
3031
+ ]),
3032
+ h9("p", { class: "absolute-voice-provider-status__description" }, model.value.description),
3033
+ model.value.providers.length ? h9("div", { class: "absolute-voice-provider-status__providers" }, model.value.providers.map((provider) => h9("article", {
3034
+ class: [
3035
+ "absolute-voice-provider-status__provider",
3036
+ `absolute-voice-provider-status__provider--${provider.status}`
3037
+ ],
3038
+ key: provider.provider
3039
+ }, [
3040
+ h9("header", [
3041
+ h9("strong", provider.label),
3042
+ h9("span", provider.status)
3043
+ ]),
3044
+ h9("p", provider.detail),
3045
+ h9("dl", provider.rows.map((row) => h9("div", { key: row.label }, [
3046
+ h9("dt", row.label),
3047
+ h9("dd", row.value)
3048
+ ])))
3049
+ ]))) : h9("p", { class: "absolute-voice-provider-status__empty" }, "Run voice traffic to see provider health."),
3050
+ model.value.error ? h9("p", { class: "absolute-voice-provider-status__error" }, model.value.error) : null
3051
+ ]);
3052
+ }
3053
+ });
3054
+ // src/vue/VoiceRoutingStatus.ts
3055
+ import { computed as computed4, defineComponent as defineComponent10, h as h10 } from "vue";
3056
+
3057
+ // src/client/routingStatus.ts
3058
+ var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
3059
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3060
+ const response = await fetchImpl(path);
3061
+ if (!response.ok) {
3062
+ throw new Error(`Voice routing status failed: HTTP ${response.status}`);
3063
+ }
3064
+ return await response.json();
3065
+ };
3066
+ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {}) => {
3067
+ const listeners = new Set;
3068
+ let closed = false;
3069
+ let timer;
3070
+ let snapshot = {
3071
+ decision: null,
3072
+ error: null,
3073
+ isLoading: false
3074
+ };
3075
+ const emit = () => {
3076
+ for (const listener of listeners) {
3077
+ listener();
3078
+ }
3079
+ };
3080
+ const refresh = async () => {
3081
+ if (closed) {
3082
+ return snapshot.decision;
3083
+ }
3084
+ snapshot = {
3085
+ ...snapshot,
3086
+ error: null,
3087
+ isLoading: true
3088
+ };
3089
+ emit();
3090
+ try {
3091
+ const decision = await fetchVoiceRoutingStatus(path, options);
3092
+ snapshot = {
3093
+ decision,
3094
+ error: null,
3095
+ isLoading: false,
3096
+ updatedAt: Date.now()
3097
+ };
3098
+ emit();
3099
+ return decision;
3100
+ } catch (error) {
3101
+ snapshot = {
3102
+ ...snapshot,
3103
+ error: error instanceof Error ? error.message : String(error),
3104
+ isLoading: false
3105
+ };
3106
+ emit();
3107
+ throw error;
3108
+ }
3109
+ };
3110
+ const close = () => {
3111
+ closed = true;
3112
+ if (timer) {
3113
+ clearInterval(timer);
3114
+ timer = undefined;
3115
+ }
3116
+ listeners.clear();
3117
+ };
3118
+ if (options.intervalMs && options.intervalMs > 0) {
3119
+ timer = setInterval(() => {
3120
+ refresh().catch(() => {});
3121
+ }, options.intervalMs);
3122
+ }
3123
+ return {
3124
+ close,
3125
+ getServerSnapshot: () => snapshot,
3126
+ getSnapshot: () => snapshot,
3127
+ refresh,
3128
+ subscribe: (listener) => {
3129
+ listeners.add(listener);
3130
+ return () => {
3131
+ listeners.delete(listener);
3132
+ };
3133
+ }
3134
+ };
3135
+ };
3136
+
3137
+ // src/client/routingStatusWidget.ts
3138
+ var DEFAULT_TITLE9 = "Voice Routing";
3139
+ var DEFAULT_DESCRIPTION9 = "Latest provider routing decision from the self-hosted trace store.";
3140
+ var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3141
+ var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
3142
+ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
3143
+ const decision = snapshot.decision;
3144
+ const rows = decision ? [
3145
+ { label: "Kind", value: decision.kind.toUpperCase() },
3146
+ { label: "Policy", value: formatValue(decision.routing, "Unknown") },
3147
+ { label: "Provider", value: formatValue(decision.provider, "Unknown") },
3148
+ {
3149
+ label: "Selected",
3150
+ value: formatValue(decision.selectedProvider, "Unknown")
3151
+ },
3152
+ {
3153
+ label: "Fallback",
3154
+ value: formatValue(decision.fallbackProvider)
3155
+ },
3156
+ { label: "Status", value: formatValue(decision.status, "unknown") },
3157
+ {
3158
+ label: "Latency budget",
3159
+ value: typeof decision.latencyBudgetMs === "number" ? `${decision.latencyBudgetMs}ms` : "None"
3160
+ }
3161
+ ] : [];
3162
+ return {
3163
+ decision,
3164
+ description: options.description ?? DEFAULT_DESCRIPTION9,
3165
+ error: snapshot.error,
3166
+ isLoading: snapshot.isLoading,
3167
+ label: snapshot.error ? "Unavailable" : decision ? `${formatValue(decision.kind).toUpperCase()} ${formatValue(decision.status, "unknown")}` : snapshot.isLoading ? "Checking" : "No routing yet",
3168
+ rows,
3169
+ status: snapshot.error ? "error" : decision ? "ready" : snapshot.isLoading ? "loading" : "empty",
3170
+ title: options.title ?? DEFAULT_TITLE9,
3171
+ updatedAt: snapshot.updatedAt
3172
+ };
3173
+ };
3174
+ var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
3175
+ const model = createVoiceRoutingStatusViewModel(snapshot, options);
3176
+ const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
3177
+ <span>${escapeHtml10(row.label)}</span>
3178
+ <strong>${escapeHtml10(row.value)}</strong>
3179
+ </div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
3180
+ return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml10(model.status)}">
3181
+ <header class="absolute-voice-routing-status__header">
3182
+ <span class="absolute-voice-routing-status__eyebrow">${escapeHtml10(model.title)}</span>
3183
+ <strong class="absolute-voice-routing-status__label">${escapeHtml10(model.label)}</strong>
3184
+ </header>
3185
+ <p class="absolute-voice-routing-status__description">${escapeHtml10(model.description)}</p>
3186
+ ${rows}
3187
+ ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml10(model.error)}</p>` : ""}
3188
+ </section>`;
3189
+ };
3190
+ 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}`;
3191
+ var mountVoiceRoutingStatus = (element, path = "/api/routing/latest", options = {}) => {
3192
+ const store = createVoiceRoutingStatusStore(path, options);
3193
+ const render = () => {
3194
+ element.innerHTML = renderVoiceRoutingStatusHTML(store.getSnapshot(), options);
3195
+ };
3196
+ const unsubscribe = store.subscribe(render);
3197
+ render();
3198
+ store.refresh().catch(() => {});
3199
+ return {
3200
+ close: () => {
3201
+ unsubscribe();
3202
+ store.close();
3203
+ },
3204
+ refresh: store.refresh
3205
+ };
3206
+ };
3207
+ var defineVoiceRoutingStatusElement = (tagName = "absolute-voice-routing-status") => {
3208
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
3209
+ return;
3210
+ }
3211
+ customElements.define(tagName, class AbsoluteVoiceRoutingStatusElement extends HTMLElement {
3212
+ mounted;
3213
+ connectedCallback() {
3214
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
3215
+ this.mounted = mountVoiceRoutingStatus(this, this.getAttribute("path") ?? "/api/routing/latest", {
3216
+ description: this.getAttribute("description") ?? undefined,
3217
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
3218
+ title: this.getAttribute("title") ?? undefined
3219
+ });
3220
+ }
3221
+ disconnectedCallback() {
3222
+ this.mounted?.close();
3223
+ this.mounted = undefined;
3224
+ }
3225
+ });
3226
+ };
3227
+
3228
+ // src/vue/useVoiceRoutingStatus.ts
3229
+ import { onUnmounted as onUnmounted10, ref as ref8, shallowRef as shallowRef9 } from "vue";
3230
+ function useVoiceRoutingStatus(path = "/api/routing/latest", options = {}) {
3231
+ const store = createVoiceRoutingStatusStore(path, options);
3232
+ const decision = shallowRef9(null);
3233
+ const error = ref8(null);
3234
+ const isLoading = ref8(false);
3235
+ const updatedAt = ref8(undefined);
3236
+ const sync = () => {
3237
+ const snapshot = store.getSnapshot();
3238
+ decision.value = snapshot.decision;
3239
+ error.value = snapshot.error;
3240
+ isLoading.value = snapshot.isLoading;
3241
+ updatedAt.value = snapshot.updatedAt;
3242
+ };
3243
+ const unsubscribe = store.subscribe(sync);
3244
+ sync();
3245
+ store.refresh().catch(() => {});
3246
+ onUnmounted10(() => {
3247
+ unsubscribe();
3248
+ store.close();
3249
+ });
3250
+ return {
3251
+ decision,
3252
+ error,
3253
+ isLoading,
3254
+ refresh: store.refresh,
3255
+ updatedAt
3256
+ };
3257
+ }
3258
+
3259
+ // src/vue/VoiceRoutingStatus.ts
3260
+ var VoiceRoutingStatus = defineComponent10({
3261
+ name: "VoiceRoutingStatus",
3262
+ props: {
3263
+ class: {
3264
+ default: "",
3265
+ type: String
3266
+ },
3267
+ description: {
3268
+ default: undefined,
3269
+ type: String
3270
+ },
3271
+ intervalMs: {
3272
+ default: 5000,
3273
+ type: Number
3274
+ },
3275
+ path: {
3276
+ default: "/api/routing/latest",
3277
+ type: String
3278
+ },
3279
+ title: {
3280
+ default: undefined,
3281
+ type: String
3282
+ }
3283
+ },
3284
+ setup(props) {
3285
+ const options = {
3286
+ description: props.description,
3287
+ intervalMs: props.intervalMs,
3288
+ title: props.title
3289
+ };
3290
+ const status = useVoiceRoutingStatus(props.path, options);
3291
+ const model = computed4(() => createVoiceRoutingStatusViewModel({
3292
+ decision: status.decision.value,
3293
+ error: status.error.value,
3294
+ isLoading: status.isLoading.value,
3295
+ updatedAt: status.updatedAt.value
3296
+ }, options));
3297
+ return () => h10("section", {
3298
+ class: [
3299
+ "absolute-voice-routing-status",
3300
+ `absolute-voice-routing-status--${model.value.status}`,
3301
+ props.class
3302
+ ]
3303
+ }, [
3304
+ h10("header", { class: "absolute-voice-routing-status__header" }, [
3305
+ h10("span", { class: "absolute-voice-routing-status__eyebrow" }, model.value.title),
3306
+ h10("strong", { class: "absolute-voice-routing-status__label" }, model.value.label)
3307
+ ]),
3308
+ h10("p", { class: "absolute-voice-routing-status__description" }, model.value.description),
3309
+ model.value.rows.length ? h10("div", { class: "absolute-voice-routing-status__grid" }, model.value.rows.map((row) => h10("div", { key: row.label }, [
3310
+ h10("span", row.label),
3311
+ h10("strong", row.value)
3312
+ ]))) : h10("p", { class: "absolute-voice-routing-status__empty" }, "Start a voice session to see the selected provider."),
3313
+ model.value.error ? h10("p", { class: "absolute-voice-routing-status__error" }, model.value.error) : null
3314
+ ]);
3315
+ }
3316
+ });
3317
+ // src/vue/useVoiceAgentSquadStatus.ts
3318
+ import { onUnmounted as onUnmounted11, ref as ref9, shallowRef as shallowRef10 } from "vue";
3319
+
3320
+ // src/client/traceTimeline.ts
3321
+ var fetchVoiceTraceTimeline = async (path = "/api/voice-traces", options = {}) => {
3322
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3323
+ const response = await fetchImpl(path);
3324
+ if (!response.ok) {
3325
+ throw new Error(`Voice trace timeline failed: HTTP ${response.status}`);
3326
+ }
3327
+ return await response.json();
3328
+ };
3329
+ var createVoiceTraceTimelineStore = (path = "/api/voice-traces", options = {}) => {
3330
+ const listeners = new Set;
3331
+ let closed = false;
3332
+ let timer;
3333
+ let snapshot = {
3334
+ error: null,
3335
+ isLoading: false,
3336
+ report: null
3337
+ };
3338
+ const emit = () => {
3339
+ for (const listener of listeners) {
3340
+ listener();
3341
+ }
3342
+ };
3343
+ const refresh = async () => {
3344
+ if (closed) {
3345
+ return snapshot.report;
3346
+ }
3347
+ snapshot = {
3348
+ ...snapshot,
3349
+ error: null,
3350
+ isLoading: true
3351
+ };
3352
+ emit();
3353
+ try {
3354
+ const report = await fetchVoiceTraceTimeline(path, options);
3355
+ snapshot = {
3356
+ error: null,
3357
+ isLoading: false,
3358
+ report,
3359
+ updatedAt: Date.now()
3360
+ };
3361
+ emit();
3362
+ return report;
3363
+ } catch (error) {
3364
+ snapshot = {
3365
+ ...snapshot,
3366
+ error: error instanceof Error ? error.message : String(error),
3367
+ isLoading: false
3368
+ };
3369
+ emit();
3370
+ throw error;
3371
+ }
3372
+ };
3373
+ const close = () => {
3374
+ closed = true;
3375
+ if (timer) {
3376
+ clearInterval(timer);
3377
+ timer = undefined;
3378
+ }
3379
+ listeners.clear();
3380
+ };
3381
+ if (options.intervalMs && options.intervalMs > 0) {
3382
+ timer = setInterval(() => {
3383
+ refresh().catch(() => {});
3384
+ }, options.intervalMs);
3385
+ }
3386
+ return {
3387
+ close,
3388
+ getServerSnapshot: () => snapshot,
3389
+ getSnapshot: () => snapshot,
3390
+ refresh,
3391
+ subscribe: (listener) => {
3392
+ listeners.add(listener);
3393
+ return () => {
3394
+ listeners.delete(listener);
3395
+ };
3396
+ }
3397
+ };
3398
+ };
3399
+
3400
+ // src/client/agentSquadStatus.ts
3401
+ var getString = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
3402
+ var getPayloadString = (event, key) => getString(event.payload?.[key]);
3403
+ var eventStatus = (event) => {
3404
+ const status = getPayloadString(event, "status");
3405
+ if (status === "blocked")
3406
+ return "blocked";
3407
+ if (status === "unknown-target")
3408
+ return "unknown-target";
3409
+ if (status === "allowed")
3410
+ return "handoff";
3411
+ return event.type === "agent.result" ? "active" : "handoff";
3412
+ };
3413
+ var deriveSessionSpecialist = (session) => {
3414
+ const events = [...session.events].sort((left, right) => left.at - right.at);
3415
+ const agentEvents = events.filter((event) => event.type === "agent.handoff" || event.type === "agent.context" || event.type === "agent.result" || event.type === "agent.model");
3416
+ const latest = agentEvents.at(-1);
3417
+ if (!latest) {
3418
+ return {
3419
+ lastEventAt: session.lastEventAt,
3420
+ sessionId: session.sessionId,
3421
+ status: "idle"
3422
+ };
3423
+ }
3424
+ const handoffEvents = events.filter((event) => event.type === "agent.handoff");
3425
+ const lastHandoff = handoffEvents.at(-1);
3426
+ const latestAgentId = getPayloadString(latest, "agentId");
3427
+ const handoffStatus = lastHandoff ? eventStatus(lastHandoff) : undefined;
3428
+ const currentTarget = handoffStatus === "blocked" || handoffStatus === "unknown-target" ? getPayloadString(lastHandoff, "fromAgentId") ?? latestAgentId : getPayloadString(lastHandoff ?? latest, "targetAgentId") ?? latestAgentId;
3429
+ return {
3430
+ fromAgentId: getPayloadString(lastHandoff ?? latest, "fromAgentId"),
3431
+ lastEventAt: latest.at,
3432
+ reason: getPayloadString(lastHandoff ?? latest, "reason") ?? getPayloadString(latest, "handoffTarget"),
3433
+ sessionId: session.sessionId,
3434
+ status: lastHandoff ? eventStatus(lastHandoff) : "active",
3435
+ summary: getPayloadString(lastHandoff ?? latest, "summary"),
3436
+ targetAgentId: currentTarget,
3437
+ turnId: latest.turnId
3438
+ };
3439
+ };
3440
+ var buildVoiceAgentSquadStatusReport = (timeline, options = {}) => {
3441
+ const sessions = (timeline?.sessions ?? []).filter((session) => !options.sessionId || session.sessionId === options.sessionId).map(deriveSessionSpecialist).sort((left, right) => (right.lastEventAt ?? 0) - (left.lastEventAt ?? 0));
3442
+ const active = sessions.filter((session) => session.status !== "idle");
3443
+ return {
3444
+ active,
3445
+ checkedAt: timeline?.checkedAt,
3446
+ current: active[0] ?? sessions[0],
3447
+ sessionCount: sessions.length,
3448
+ sessions
3449
+ };
3450
+ };
3451
+ var createVoiceAgentSquadStatusStore = (path = "/api/voice-traces", options = {}) => {
3452
+ const timelineStore = createVoiceTraceTimelineStore(path, options);
3453
+ const getReport = () => buildVoiceAgentSquadStatusReport(timelineStore.getSnapshot().report, {
3454
+ sessionId: options.sessionId
3455
+ });
3456
+ const getSnapshot = () => {
3457
+ const snapshot = timelineStore.getSnapshot();
3458
+ return {
3459
+ error: snapshot.error,
3460
+ isLoading: snapshot.isLoading,
3461
+ report: getReport(),
3462
+ updatedAt: snapshot.updatedAt
3463
+ };
3464
+ };
3465
+ return {
3466
+ close: timelineStore.close,
3467
+ getServerSnapshot: getSnapshot,
3468
+ getSnapshot,
3469
+ refresh: timelineStore.refresh,
3470
+ subscribe: timelineStore.subscribe
3471
+ };
3472
+ };
3473
+
3474
+ // src/vue/useVoiceAgentSquadStatus.ts
3475
+ function useVoiceAgentSquadStatus(path = "/api/voice-traces", options = {}) {
3476
+ const store = createVoiceAgentSquadStatusStore(path, options);
3477
+ const current = shallowRef10(undefined);
3478
+ const error = ref9(null);
3479
+ const isLoading = ref9(false);
3480
+ const report = shallowRef10(undefined);
3481
+ const updatedAt = ref9(undefined);
3482
+ const sync = () => {
3483
+ const snapshot = store.getSnapshot();
3484
+ current.value = snapshot.report.current;
3485
+ error.value = snapshot.error;
3486
+ isLoading.value = snapshot.isLoading;
3487
+ report.value = snapshot.report;
3488
+ updatedAt.value = snapshot.updatedAt;
3489
+ };
3490
+ const unsubscribe = store.subscribe(sync);
3491
+ sync();
3492
+ if (typeof window !== "undefined") {
3493
+ store.refresh().catch(() => {});
3494
+ }
3495
+ onUnmounted11(() => {
3496
+ unsubscribe();
3497
+ store.close();
3498
+ });
3499
+ return {
3500
+ current,
3501
+ error,
3502
+ isLoading,
3503
+ refresh: store.refresh,
3504
+ report,
3505
+ updatedAt
3506
+ };
3507
+ }
3508
+ // src/vue/VoiceTurnLatency.ts
3509
+ import { computed as computed5, defineComponent as defineComponent11, h as h11 } from "vue";
3510
+
3511
+ // src/client/turnLatency.ts
3512
+ var fetchVoiceTurnLatency = async (path = "/api/turn-latency", options = {}) => {
3513
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3514
+ const response = await fetchImpl(path);
3515
+ if (!response.ok) {
3516
+ throw new Error(`Voice turn latency failed: HTTP ${response.status}`);
3517
+ }
3518
+ return await response.json();
3519
+ };
3520
+ var runVoiceTurnLatencyProof = async (path, options = {}) => {
3521
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3522
+ const response = await fetchImpl(path, { method: "POST" });
3523
+ if (!response.ok) {
3524
+ throw new Error(`Voice turn latency proof failed: HTTP ${response.status}`);
3525
+ }
3526
+ return response.json();
3527
+ };
3528
+ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) => {
3529
+ const listeners = new Set;
3530
+ let closed = false;
3531
+ let timer;
3532
+ let snapshot = {
3533
+ error: null,
3534
+ isLoading: false
3535
+ };
3536
+ const emit = () => {
3537
+ for (const listener of listeners) {
3538
+ listener();
3539
+ }
3540
+ };
3541
+ const refresh = async () => {
3542
+ if (closed) {
3543
+ return snapshot.report;
3544
+ }
3545
+ snapshot = { ...snapshot, error: null, isLoading: true };
3546
+ emit();
3547
+ try {
3548
+ const report = await fetchVoiceTurnLatency(path, options);
3549
+ snapshot = {
3550
+ error: null,
3551
+ isLoading: false,
3552
+ report,
3553
+ updatedAt: Date.now()
3554
+ };
3555
+ emit();
3556
+ return report;
3557
+ } catch (error) {
3558
+ snapshot = {
3559
+ ...snapshot,
3560
+ error: error instanceof Error ? error.message : String(error),
3561
+ isLoading: false
3562
+ };
3563
+ emit();
3564
+ throw error;
3565
+ }
3566
+ };
3567
+ const runProof = async () => {
3568
+ if (!options.proofPath) {
3569
+ throw new Error("Voice turn latency proof path is not configured.");
3570
+ }
3571
+ snapshot = { ...snapshot, error: null, isLoading: true };
3572
+ emit();
3573
+ try {
3574
+ await runVoiceTurnLatencyProof(options.proofPath, options);
3575
+ return await refresh();
3576
+ } catch (error) {
3577
+ snapshot = {
3578
+ ...snapshot,
3579
+ error: error instanceof Error ? error.message : String(error),
3580
+ isLoading: false
3581
+ };
3582
+ emit();
3583
+ throw error;
3584
+ }
3585
+ };
3586
+ const close = () => {
3587
+ closed = true;
3588
+ if (timer) {
3589
+ clearInterval(timer);
3590
+ timer = undefined;
3591
+ }
3592
+ listeners.clear();
3593
+ };
3594
+ if (options.intervalMs && options.intervalMs > 0) {
3595
+ timer = setInterval(() => {
3596
+ refresh().catch(() => {});
3597
+ }, options.intervalMs);
3598
+ }
3599
+ return {
3600
+ close,
3601
+ getServerSnapshot: () => snapshot,
3602
+ getSnapshot: () => snapshot,
3603
+ refresh,
3604
+ runProof,
3605
+ subscribe: (listener) => {
3606
+ listeners.add(listener);
3607
+ return () => {
3608
+ listeners.delete(listener);
3609
+ };
3610
+ }
3611
+ };
3612
+ };
3613
+
3614
+ // src/client/turnLatencyWidget.ts
3615
+ var DEFAULT_TITLE10 = "Turn Latency";
3616
+ var DEFAULT_DESCRIPTION10 = "Per-turn timing from first transcript to commit and assistant response start.";
3617
+ var DEFAULT_PROOF_LABEL = "Run latency proof";
3618
+ var escapeHtml11 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3619
+ var formatMs2 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
3620
+ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
3621
+ const turns = (snapshot.report?.turns ?? []).map((turn) => ({
3622
+ ...turn,
3623
+ label: turn.text || "Empty turn",
3624
+ rows: turn.stages.map((stage) => ({
3625
+ label: stage.label,
3626
+ value: formatMs2(stage.valueMs)
3627
+ }))
3628
+ }));
3629
+ const warningCount = snapshot.report?.warnings ?? turns.filter((turn) => turn.status === "warn").length;
3630
+ const failedCount = snapshot.report?.failed ?? turns.filter((turn) => turn.status === "fail").length;
3631
+ return {
3632
+ description: options.description ?? DEFAULT_DESCRIPTION10,
3633
+ error: snapshot.error,
3634
+ isLoading: snapshot.isLoading,
3635
+ label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} slow` : warningCount > 0 ? `${warningCount} warnings` : `avg ${formatMs2(snapshot.report?.averageTotalMs)}` : snapshot.isLoading ? "Checking" : "No turns",
3636
+ proofLabel: options.proofPath ? options.proofLabel ?? DEFAULT_PROOF_LABEL : undefined,
3637
+ showProofAction: Boolean(options.proofPath),
3638
+ status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
3639
+ title: options.title ?? DEFAULT_TITLE10,
3640
+ turns,
3641
+ updatedAt: snapshot.updatedAt
3642
+ };
3643
+ };
3644
+ var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
3645
+ const model = createVoiceTurnLatencyViewModel(snapshot, options);
3646
+ 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--${escapeHtml11(turn.status)}">
3647
+ <header>
3648
+ <strong>${escapeHtml11(turn.label)}</strong>
3649
+ <span>${escapeHtml11(turn.status)}</span>
3650
+ </header>
3651
+ <dl>${turn.rows.map((row) => `<div>
3652
+ <dt>${escapeHtml11(row.label)}</dt>
3653
+ <dd>${escapeHtml11(row.value)}</dd>
3654
+ </div>`).join("")}</dl>
3655
+ </article>`).join("")}</div>` : '<p class="absolute-voice-turn-latency__empty">Complete a voice turn to see latency diagnostics.</p>';
3656
+ return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml11(model.status)}">
3657
+ <header class="absolute-voice-turn-latency__header">
3658
+ <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml11(model.title)}</span>
3659
+ <strong class="absolute-voice-turn-latency__label">${escapeHtml11(model.label)}</strong>
3660
+ </header>
3661
+ <p class="absolute-voice-turn-latency__description">${escapeHtml11(model.description)}</p>
3662
+ ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml11(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
3663
+ ${turns}
3664
+ ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml11(model.error)}</p>` : ""}
3665
+ </section>`;
3666
+ };
3667
+ var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {}) => {
3668
+ const store = createVoiceTurnLatencyStore(path, options);
3669
+ const render = () => {
3670
+ element.innerHTML = renderVoiceTurnLatencyHTML(store.getSnapshot(), options);
3671
+ };
3672
+ const handleClick = (event) => {
3673
+ const target = event.target;
3674
+ if (target instanceof Element && target.closest("[data-absolute-voice-turn-latency-proof]")) {
3675
+ store.runProof().catch(() => {});
3676
+ }
3677
+ };
3678
+ const unsubscribe = store.subscribe(render);
3679
+ element.addEventListener("click", handleClick);
3680
+ render();
3681
+ store.refresh().catch(() => {});
3682
+ return {
3683
+ close: () => {
3684
+ element.removeEventListener("click", handleClick);
3685
+ unsubscribe();
3686
+ store.close();
3687
+ },
3688
+ refresh: store.refresh
3689
+ };
3690
+ };
3691
+ var defineVoiceTurnLatencyElement = (tagName = "absolute-voice-turn-latency") => {
3692
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
3693
+ return;
3694
+ }
3695
+ customElements.define(tagName, class AbsoluteVoiceTurnLatencyElement extends HTMLElement {
3696
+ mounted;
3697
+ connectedCallback() {
3698
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
3699
+ this.mounted = mountVoiceTurnLatency(this, this.getAttribute("path") ?? "/api/turn-latency", {
3700
+ description: this.getAttribute("description") ?? undefined,
3701
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
3702
+ proofLabel: this.getAttribute("proof-label") ?? undefined,
3703
+ proofPath: this.getAttribute("proof-path") ?? undefined,
3704
+ title: this.getAttribute("title") ?? undefined
3705
+ });
3706
+ }
3707
+ disconnectedCallback() {
3708
+ this.mounted?.close();
3709
+ this.mounted = undefined;
3710
+ }
3711
+ });
3712
+ };
3713
+
3714
+ // src/vue/useVoiceTurnLatency.ts
3715
+ import { onUnmounted as onUnmounted12, shallowRef as shallowRef11 } from "vue";
3716
+ function useVoiceTurnLatency(path = "/api/turn-latency", options = {}) {
3717
+ const store = createVoiceTurnLatencyStore(path, options);
3718
+ const error = shallowRef11(null);
3719
+ const isLoading = shallowRef11(false);
3720
+ const report = shallowRef11();
3721
+ const updatedAt = shallowRef11(undefined);
3722
+ const sync = () => {
3723
+ const snapshot = store.getSnapshot();
3724
+ error.value = snapshot.error;
3725
+ isLoading.value = snapshot.isLoading;
3726
+ report.value = snapshot.report;
3727
+ updatedAt.value = snapshot.updatedAt;
3728
+ };
3729
+ const unsubscribe = store.subscribe(sync);
3730
+ sync();
3731
+ store.refresh().catch(() => {});
3732
+ onUnmounted12(() => {
3733
+ unsubscribe();
3734
+ store.close();
3735
+ });
3736
+ return {
3737
+ error,
3738
+ isLoading,
3739
+ refresh: store.refresh,
3740
+ report,
3741
+ runProof: store.runProof,
3742
+ updatedAt
3743
+ };
3744
+ }
3745
+
3746
+ // src/vue/VoiceTurnLatency.ts
3747
+ var VoiceTurnLatency = defineComponent11({
3748
+ name: "VoiceTurnLatency",
3749
+ props: {
3750
+ class: { default: "", type: String },
3751
+ description: { default: undefined, type: String },
3752
+ intervalMs: { default: 5000, type: Number },
3753
+ path: { default: "/api/turn-latency", type: String },
3754
+ proofLabel: { default: undefined, type: String },
3755
+ proofPath: { default: undefined, type: String },
3756
+ title: { default: undefined, type: String }
3757
+ },
3758
+ setup(props) {
3759
+ const options = {
3760
+ description: props.description,
3761
+ intervalMs: props.intervalMs,
3762
+ proofLabel: props.proofLabel,
3763
+ proofPath: props.proofPath,
3764
+ title: props.title
3765
+ };
3766
+ const latency = useVoiceTurnLatency(props.path, options);
3767
+ const model = computed5(() => createVoiceTurnLatencyViewModel({
3768
+ error: latency.error.value,
3769
+ isLoading: latency.isLoading.value,
3770
+ report: latency.report.value,
3771
+ updatedAt: latency.updatedAt.value
3772
+ }, options));
3773
+ return () => h11("section", {
3774
+ class: [
3775
+ "absolute-voice-turn-latency",
3776
+ `absolute-voice-turn-latency--${model.value.status}`,
3777
+ props.class
3778
+ ]
3779
+ }, [
3780
+ h11("header", { class: "absolute-voice-turn-latency__header" }, [
3781
+ h11("span", { class: "absolute-voice-turn-latency__eyebrow" }, model.value.title),
3782
+ h11("strong", { class: "absolute-voice-turn-latency__label" }, model.value.label)
3783
+ ]),
3784
+ h11("p", { class: "absolute-voice-turn-latency__description" }, model.value.description),
3785
+ model.value.showProofAction ? h11("button", {
3786
+ class: "absolute-voice-turn-latency__proof",
3787
+ onClick: () => {
3788
+ latency.runProof().catch(() => {});
3789
+ },
3790
+ type: "button"
3791
+ }, model.value.proofLabel) : null,
3792
+ model.value.turns.length ? h11("div", { class: "absolute-voice-turn-latency__turns" }, model.value.turns.map((turn) => h11("article", {
3793
+ class: [
3794
+ "absolute-voice-turn-latency__turn",
3795
+ `absolute-voice-turn-latency__turn--${turn.status}`
3796
+ ],
3797
+ key: `${turn.sessionId}:${turn.turnId}`
3798
+ }, [
3799
+ h11("header", [
3800
+ h11("strong", turn.label),
3801
+ h11("span", turn.status)
3802
+ ]),
3803
+ h11("dl", turn.rows.map((row) => h11("div", { key: row.label }, [
3804
+ h11("dt", row.label),
3805
+ h11("dd", row.value)
3806
+ ])))
3807
+ ]))) : h11("p", { class: "absolute-voice-turn-latency__empty" }, "Complete a voice turn to see latency diagnostics."),
3808
+ model.value.error ? h11("p", { class: "absolute-voice-turn-latency__error" }, model.value.error) : null
3809
+ ]);
3810
+ }
3811
+ });
3812
+ // src/vue/VoiceTurnQuality.ts
3813
+ import { computed as computed6, defineComponent as defineComponent12, h as h12 } from "vue";
3814
+
3815
+ // src/client/turnQuality.ts
3816
+ var fetchVoiceTurnQuality = async (path = "/api/turn-quality", options = {}) => {
3817
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3818
+ const response = await fetchImpl(path);
3819
+ if (!response.ok) {
3820
+ throw new Error(`Voice turn quality failed: HTTP ${response.status}`);
3821
+ }
3822
+ return await response.json();
3823
+ };
3824
+ var createVoiceTurnQualityStore = (path = "/api/turn-quality", options = {}) => {
3825
+ const listeners = new Set;
3826
+ let closed = false;
3827
+ let timer;
3828
+ let snapshot = {
3829
+ error: null,
3830
+ isLoading: false
3831
+ };
3832
+ const emit = () => {
3833
+ for (const listener of listeners) {
3834
+ listener();
3835
+ }
3836
+ };
3837
+ const refresh = async () => {
3838
+ if (closed) {
3839
+ return snapshot.report;
3840
+ }
3841
+ snapshot = {
3842
+ ...snapshot,
3843
+ error: null,
3844
+ isLoading: true
3845
+ };
3846
+ emit();
3847
+ try {
3848
+ const report = await fetchVoiceTurnQuality(path, options);
3849
+ snapshot = {
3850
+ error: null,
3851
+ isLoading: false,
3852
+ report,
3853
+ updatedAt: Date.now()
3854
+ };
3855
+ emit();
3856
+ return report;
3857
+ } catch (error) {
3858
+ snapshot = {
3859
+ ...snapshot,
3860
+ error: error instanceof Error ? error.message : String(error),
3861
+ isLoading: false
3862
+ };
3863
+ emit();
3864
+ throw error;
3865
+ }
3866
+ };
3867
+ const close = () => {
3868
+ closed = true;
3869
+ if (timer) {
3870
+ clearInterval(timer);
3871
+ timer = undefined;
3872
+ }
3873
+ listeners.clear();
3874
+ };
3875
+ if (options.intervalMs && options.intervalMs > 0) {
3876
+ timer = setInterval(() => {
3877
+ refresh().catch(() => {});
3878
+ }, options.intervalMs);
3879
+ }
3880
+ return {
3881
+ close,
3882
+ getServerSnapshot: () => snapshot,
3883
+ getSnapshot: () => snapshot,
3884
+ refresh,
3885
+ subscribe: (listener) => {
3886
+ listeners.add(listener);
3887
+ return () => {
3888
+ listeners.delete(listener);
3889
+ };
3890
+ }
3891
+ };
3892
+ };
3893
+
3894
+ // src/client/turnQualityWidget.ts
3895
+ var DEFAULT_TITLE11 = "Turn Quality";
3896
+ var DEFAULT_DESCRIPTION11 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
3897
+ var escapeHtml12 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3898
+ var formatConfidence = (value) => typeof value === "number" ? `${Math.round(value * 100)}%` : "n/a";
3899
+ var formatMaybe = (value) => value === undefined || value === "" ? "n/a" : String(value);
3900
+ var getTurnDetail = (turn) => {
3901
+ if (turn.status === "fail") {
3902
+ return "Empty or unusable committed turn; inspect transcripts and adapter events.";
3903
+ }
3904
+ if (turn.fallbackUsed) {
3905
+ return `Fallback STT selected${turn.fallbackSelectionReason ? ` by ${turn.fallbackSelectionReason}` : ""}.`;
3906
+ }
3907
+ if (turn.correctionChanged) {
3908
+ return `Correction changed the turn${turn.correctionProvider ? ` via ${turn.correctionProvider}` : ""}.`;
3909
+ }
3910
+ if (turn.status === "warn") {
3911
+ return "Turn completed with quality warnings.";
3912
+ }
3913
+ if (turn.status === "unknown") {
3914
+ return "No quality diagnostics were recorded for this turn.";
3915
+ }
3916
+ return "Turn quality looks healthy.";
3917
+ };
3918
+ var createVoiceTurnQualityViewModel = (snapshot, options = {}) => {
3919
+ const turns = (snapshot.report?.turns ?? []).map((turn) => ({
3920
+ ...turn,
3921
+ detail: getTurnDetail(turn),
3922
+ label: turn.text || "Empty turn",
3923
+ rows: [
3924
+ { label: "Source", value: turn.source ?? "unknown" },
3925
+ { label: "Confidence", value: formatConfidence(turn.averageConfidence) },
3926
+ { label: "Fallback", value: turn.fallbackUsed ? "Yes" : "No" },
3927
+ { label: "Correction", value: turn.correctionChanged ? "Changed" : "None" },
3928
+ { label: "Transcripts", value: `${turn.selectedTranscriptCount} selected` },
3929
+ { label: "Cost", value: formatMaybe(turn.costUnits) }
3930
+ ]
3931
+ }));
3932
+ const warningCount = snapshot.report?.warnings ?? turns.filter((turn) => turn.status === "warn").length;
3933
+ const failedCount = snapshot.report?.failed ?? turns.filter((turn) => turn.status === "fail").length;
3934
+ return {
3935
+ description: options.description ?? DEFAULT_DESCRIPTION11,
3936
+ error: snapshot.error,
3937
+ isLoading: snapshot.isLoading,
3938
+ label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} failed` : warningCount > 0 ? `${warningCount} warnings` : `${turns.length} healthy` : snapshot.isLoading ? "Checking" : "No turns",
3939
+ status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
3940
+ title: options.title ?? DEFAULT_TITLE11,
3941
+ turns,
3942
+ updatedAt: snapshot.updatedAt
3943
+ };
3944
+ };
3945
+ var renderVoiceTurnQualityHTML = (snapshot, options = {}) => {
3946
+ const model = createVoiceTurnQualityViewModel(snapshot, options);
3947
+ 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--${escapeHtml12(turn.status)}">
3948
+ <header>
3949
+ <strong>${escapeHtml12(turn.label)}</strong>
3950
+ <span>${escapeHtml12(turn.status)}</span>
3951
+ </header>
3952
+ <p>${escapeHtml12(turn.detail)}</p>
3953
+ <dl>${turn.rows.map((row) => `<div>
3954
+ <dt>${escapeHtml12(row.label)}</dt>
3955
+ <dd>${escapeHtml12(row.value)}</dd>
3956
+ </div>`).join("")}</dl>
3957
+ </article>`).join("")}</div>` : '<p class="absolute-voice-turn-quality__empty">Complete a voice turn to see STT quality diagnostics.</p>';
3958
+ return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml12(model.status)}">
3959
+ <header class="absolute-voice-turn-quality__header">
3960
+ <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml12(model.title)}</span>
3961
+ <strong class="absolute-voice-turn-quality__label">${escapeHtml12(model.label)}</strong>
3962
+ </header>
3963
+ <p class="absolute-voice-turn-quality__description">${escapeHtml12(model.description)}</p>
3964
+ ${turns}
3965
+ ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml12(model.error)}</p>` : ""}
3966
+ </section>`;
3967
+ };
3968
+ 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}`;
3969
+ var mountVoiceTurnQuality = (element, path = "/api/turn-quality", options = {}) => {
3970
+ const store = createVoiceTurnQualityStore(path, options);
3971
+ const render = () => {
3972
+ element.innerHTML = renderVoiceTurnQualityHTML(store.getSnapshot(), options);
3973
+ };
3974
+ const unsubscribe = store.subscribe(render);
3975
+ render();
3976
+ store.refresh().catch(() => {});
3977
+ return {
3978
+ close: () => {
3979
+ unsubscribe();
3980
+ store.close();
3981
+ },
3982
+ refresh: store.refresh
3983
+ };
3984
+ };
3985
+ var defineVoiceTurnQualityElement = (tagName = "absolute-voice-turn-quality") => {
3986
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
3987
+ return;
3988
+ }
3989
+ customElements.define(tagName, class AbsoluteVoiceTurnQualityElement extends HTMLElement {
3990
+ mounted;
3991
+ connectedCallback() {
3992
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
3993
+ this.mounted = mountVoiceTurnQuality(this, this.getAttribute("path") ?? "/api/turn-quality", {
3994
+ description: this.getAttribute("description") ?? undefined,
3995
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
3996
+ title: this.getAttribute("title") ?? undefined
3997
+ });
3998
+ }
3999
+ disconnectedCallback() {
4000
+ this.mounted?.close();
4001
+ this.mounted = undefined;
4002
+ }
4003
+ });
4004
+ };
4005
+
4006
+ // src/vue/useVoiceTurnQuality.ts
4007
+ import { onUnmounted as onUnmounted13, shallowRef as shallowRef12 } from "vue";
4008
+ function useVoiceTurnQuality(path = "/api/turn-quality", options = {}) {
4009
+ const store = createVoiceTurnQualityStore(path, options);
4010
+ const error = shallowRef12(null);
4011
+ const isLoading = shallowRef12(false);
4012
+ const report = shallowRef12();
4013
+ const updatedAt = shallowRef12(undefined);
4014
+ const sync = () => {
4015
+ const snapshot = store.getSnapshot();
4016
+ error.value = snapshot.error;
4017
+ isLoading.value = snapshot.isLoading;
4018
+ report.value = snapshot.report;
4019
+ updatedAt.value = snapshot.updatedAt;
4020
+ };
4021
+ const unsubscribe = store.subscribe(sync);
4022
+ sync();
4023
+ store.refresh().catch(() => {});
4024
+ onUnmounted13(() => {
4025
+ unsubscribe();
4026
+ store.close();
4027
+ });
4028
+ return { error, isLoading, refresh: store.refresh, report, updatedAt };
4029
+ }
4030
+
4031
+ // src/vue/VoiceTurnQuality.ts
4032
+ var VoiceTurnQuality = defineComponent12({
4033
+ name: "VoiceTurnQuality",
4034
+ props: {
4035
+ class: { default: "", type: String },
4036
+ description: { default: undefined, type: String },
4037
+ intervalMs: { default: 5000, type: Number },
4038
+ path: { default: "/api/turn-quality", type: String },
4039
+ title: { default: undefined, type: String }
4040
+ },
4041
+ setup(props) {
4042
+ const options = {
4043
+ description: props.description,
4044
+ intervalMs: props.intervalMs,
4045
+ title: props.title
4046
+ };
4047
+ const quality = useVoiceTurnQuality(props.path, options);
4048
+ const model = computed6(() => createVoiceTurnQualityViewModel({
4049
+ error: quality.error.value,
4050
+ isLoading: quality.isLoading.value,
4051
+ report: quality.report.value,
4052
+ updatedAt: quality.updatedAt.value
4053
+ }, options));
4054
+ return () => h12("section", {
4055
+ class: [
4056
+ "absolute-voice-turn-quality",
4057
+ `absolute-voice-turn-quality--${model.value.status}`,
4058
+ props.class
4059
+ ]
4060
+ }, [
4061
+ h12("header", { class: "absolute-voice-turn-quality__header" }, [
4062
+ h12("span", { class: "absolute-voice-turn-quality__eyebrow" }, model.value.title),
4063
+ h12("strong", { class: "absolute-voice-turn-quality__label" }, model.value.label)
4064
+ ]),
4065
+ h12("p", { class: "absolute-voice-turn-quality__description" }, model.value.description),
4066
+ model.value.turns.length ? h12("div", { class: "absolute-voice-turn-quality__turns" }, model.value.turns.map((turn) => h12("article", {
4067
+ class: [
4068
+ "absolute-voice-turn-quality__turn",
4069
+ `absolute-voice-turn-quality__turn--${turn.status}`
4070
+ ],
4071
+ key: `${turn.sessionId}:${turn.turnId}`
4072
+ }, [
4073
+ h12("header", [
4074
+ h12("strong", turn.label),
4075
+ h12("span", turn.status)
4076
+ ]),
4077
+ h12("p", turn.detail),
4078
+ h12("dl", turn.rows.map((row) => h12("div", { key: row.label }, [
4079
+ h12("dt", row.label),
4080
+ h12("dd", row.value)
4081
+ ])))
4082
+ ]))) : h12("p", { class: "absolute-voice-turn-quality__empty" }, "Complete a voice turn to see STT quality diagnostics."),
4083
+ model.value.error ? h12("p", { class: "absolute-voice-turn-quality__error" }, model.value.error) : null
4084
+ ]);
4085
+ }
4086
+ });
4087
+ // src/vue/useVoiceLiveOps.ts
4088
+ import { onUnmounted as onUnmounted14, ref as ref10, shallowRef as shallowRef13 } from "vue";
4089
+
4090
+ // src/client/liveOps.ts
4091
+ var postVoiceLiveOpsAction = async (input, options = {}) => {
4092
+ if (!input.sessionId) {
4093
+ throw new Error("Start a voice session before running live ops actions.");
4094
+ }
4095
+ const fetchImpl = options.fetch ?? globalThis.fetch;
4096
+ const response = await fetchImpl(options.actionPath ?? "/api/voice/live-ops/action", {
4097
+ body: JSON.stringify(input),
4098
+ headers: {
4099
+ "Content-Type": "application/json"
4100
+ },
4101
+ method: "POST"
4102
+ });
4103
+ const payload = await response.json().catch(() => null);
4104
+ if (!response.ok || !payload?.ok) {
4105
+ const message = payload && typeof payload === "object" && "error" in payload ? String(payload.error) : `Voice live ops action failed: HTTP ${response.status}`;
4106
+ throw new Error(message);
4107
+ }
4108
+ return payload;
4109
+ };
4110
+ var createVoiceLiveOpsStore = (options = {}) => {
4111
+ const listeners = new Set;
4112
+ let closed = false;
4113
+ let snapshot = {
4114
+ error: null,
4115
+ isRunning: false
4116
+ };
4117
+ const emit = () => {
4118
+ for (const listener of listeners) {
4119
+ listener();
4120
+ }
4121
+ };
4122
+ const run = async (input) => {
4123
+ if (closed) {
4124
+ return snapshot.lastResult;
4125
+ }
4126
+ snapshot = {
4127
+ ...snapshot,
4128
+ error: null,
4129
+ isRunning: true,
4130
+ runningAction: input.action
4131
+ };
4132
+ emit();
4133
+ try {
4134
+ const result = await postVoiceLiveOpsAction(input, options);
4135
+ await options.onControl?.(result);
4136
+ snapshot = {
4137
+ ...snapshot,
4138
+ error: null,
4139
+ isRunning: false,
4140
+ lastResult: result,
4141
+ runningAction: undefined,
4142
+ updatedAt: Date.now()
4143
+ };
4144
+ emit();
4145
+ return result;
4146
+ } catch (error) {
4147
+ snapshot = {
4148
+ ...snapshot,
4149
+ error: error instanceof Error ? error.message : String(error),
4150
+ isRunning: false,
4151
+ runningAction: undefined,
4152
+ updatedAt: Date.now()
4153
+ };
4154
+ emit();
4155
+ throw error;
4156
+ }
4157
+ };
4158
+ const close = () => {
4159
+ closed = true;
4160
+ listeners.clear();
4161
+ };
4162
+ return {
4163
+ close,
4164
+ getServerSnapshot: () => snapshot,
4165
+ getSnapshot: () => snapshot,
4166
+ run,
4167
+ subscribe: (listener) => {
4168
+ listeners.add(listener);
4169
+ return () => {
4170
+ listeners.delete(listener);
4171
+ };
4172
+ }
4173
+ };
4174
+ };
4175
+
4176
+ // src/vue/useVoiceLiveOps.ts
4177
+ function useVoiceLiveOps(options = {}) {
4178
+ const store = createVoiceLiveOpsStore(options);
4179
+ const error = ref10(null);
4180
+ const isRunning = ref10(false);
4181
+ const lastResult = shallowRef13(undefined);
4182
+ const runningAction = ref10(undefined);
4183
+ const updatedAt = ref10(undefined);
4184
+ const sync = () => {
4185
+ const snapshot = store.getSnapshot();
4186
+ error.value = snapshot.error;
4187
+ isRunning.value = snapshot.isRunning;
4188
+ lastResult.value = snapshot.lastResult;
4189
+ runningAction.value = snapshot.runningAction;
4190
+ updatedAt.value = snapshot.updatedAt;
4191
+ };
4192
+ const unsubscribe = store.subscribe(sync);
4193
+ sync();
4194
+ onUnmounted14(() => {
4195
+ unsubscribe();
4196
+ store.close();
4197
+ });
4198
+ return {
4199
+ error,
4200
+ isRunning,
4201
+ lastResult,
4202
+ run: store.run,
4203
+ runningAction,
4204
+ updatedAt
4205
+ };
4206
+ }
4207
+ // src/vue/useVoiceCampaignDialerProof.ts
4208
+ import { onUnmounted as onUnmounted15, shallowRef as shallowRef14 } from "vue";
4209
+
4210
+ // src/client/campaignDialerProof.ts
4211
+ var fetchVoiceCampaignDialerProofStatus = async (path = "/api/voice/campaigns/dialer-proof", options = {}) => {
4212
+ const fetchImpl = options.fetch ?? globalThis.fetch;
4213
+ const response = await fetchImpl(path);
4214
+ if (!response.ok) {
4215
+ throw new Error(`Voice campaign dialer proof status failed: HTTP ${response.status}`);
4216
+ }
4217
+ return await response.json();
4218
+ };
4219
+ var runVoiceCampaignDialerProofAction = async (path = "/api/voice/campaigns/dialer-proof", options = {}) => {
4220
+ const fetchImpl = options.fetch ?? globalThis.fetch;
4221
+ const response = await fetchImpl(path, { method: "POST" });
4222
+ if (!response.ok) {
4223
+ throw new Error(`Voice campaign dialer proof failed: HTTP ${response.status}`);
4224
+ }
4225
+ return await response.json();
4226
+ };
4227
+ var createVoiceCampaignDialerProofStore = (path = "/api/voice/campaigns/dialer-proof", options = {}) => {
4228
+ const listeners = new Set;
4229
+ let closed = false;
4230
+ let timer;
4231
+ let snapshot = {
4232
+ error: null,
4233
+ isLoading: false
4234
+ };
4235
+ const emit = () => {
4236
+ for (const listener of listeners) {
4237
+ listener();
4238
+ }
4239
+ };
4240
+ const refresh = async () => {
4241
+ if (closed) {
4242
+ return snapshot.status;
4243
+ }
4244
+ snapshot = { ...snapshot, error: null, isLoading: true };
4245
+ emit();
4246
+ try {
4247
+ const status = await fetchVoiceCampaignDialerProofStatus(path, options);
4248
+ snapshot = {
4249
+ ...snapshot,
4250
+ error: null,
4251
+ isLoading: false,
4252
+ status,
4253
+ updatedAt: Date.now()
4254
+ };
4255
+ emit();
4256
+ return status;
4257
+ } catch (error) {
4258
+ snapshot = {
4259
+ ...snapshot,
4260
+ error: error instanceof Error ? error.message : String(error),
4261
+ isLoading: false
4262
+ };
4263
+ emit();
4264
+ throw error;
4265
+ }
4266
+ };
4267
+ const runProof = async () => {
4268
+ const runPath = options.runPath ?? snapshot.status?.runPath ?? path;
4269
+ snapshot = { ...snapshot, error: null, isLoading: true };
4270
+ emit();
4271
+ try {
4272
+ const report = await runVoiceCampaignDialerProofAction(runPath, options);
4273
+ snapshot = {
4274
+ ...snapshot,
4275
+ error: null,
4276
+ isLoading: false,
4277
+ report,
4278
+ status: {
4279
+ generatedAt: Date.now(),
4280
+ mode: report.mode,
4281
+ ok: report.ok,
4282
+ providers: report.providers.map((provider) => provider.provider),
4283
+ runPath,
4284
+ safe: true
4285
+ },
4286
+ updatedAt: Date.now()
4287
+ };
4288
+ emit();
4289
+ return report;
4290
+ } catch (error) {
4291
+ snapshot = {
4292
+ ...snapshot,
4293
+ error: error instanceof Error ? error.message : String(error),
4294
+ isLoading: false
4295
+ };
4296
+ emit();
4297
+ throw error;
4298
+ }
4299
+ };
4300
+ const close = () => {
4301
+ closed = true;
4302
+ if (timer) {
4303
+ clearInterval(timer);
4304
+ timer = undefined;
4305
+ }
4306
+ listeners.clear();
4307
+ };
4308
+ if (options.intervalMs && options.intervalMs > 0) {
4309
+ timer = setInterval(() => {
4310
+ refresh().catch(() => {});
4311
+ }, options.intervalMs);
4312
+ }
4313
+ return {
4314
+ close,
4315
+ getServerSnapshot: () => snapshot,
4316
+ getSnapshot: () => snapshot,
4317
+ refresh,
4318
+ runProof,
4319
+ subscribe: (listener) => {
4320
+ listeners.add(listener);
4321
+ return () => {
4322
+ listeners.delete(listener);
4323
+ };
4324
+ }
4325
+ };
4326
+ };
4327
+
4328
+ // src/vue/useVoiceCampaignDialerProof.ts
4329
+ function useVoiceCampaignDialerProof(path = "/api/voice/campaigns/dialer-proof", options = {}) {
4330
+ const store = createVoiceCampaignDialerProofStore(path, options);
4331
+ const error = shallowRef14(null);
4332
+ const isLoading = shallowRef14(false);
4333
+ const report = shallowRef14();
4334
+ const status = shallowRef14();
4335
+ const updatedAt = shallowRef14(undefined);
4336
+ const sync = () => {
4337
+ const snapshot = store.getSnapshot();
4338
+ error.value = snapshot.error;
4339
+ isLoading.value = snapshot.isLoading;
4340
+ report.value = snapshot.report;
4341
+ status.value = snapshot.status;
4342
+ updatedAt.value = snapshot.updatedAt;
4343
+ };
4344
+ const unsubscribe = store.subscribe(sync);
4345
+ sync();
4346
+ if (typeof window !== "undefined") {
4347
+ store.refresh().catch(() => {});
4348
+ }
4349
+ onUnmounted15(() => {
4350
+ unsubscribe();
4351
+ store.close();
4352
+ });
4353
+ return {
4354
+ error,
4355
+ isLoading,
4356
+ refresh: store.refresh,
4357
+ report,
4358
+ runProof: store.runProof,
4359
+ status,
4360
+ updatedAt
4361
+ };
4362
+ }
72
4363
  // src/vue/useVoiceStream.ts
73
- import { onUnmounted, ref, shallowRef } from "vue";
4364
+ import { onUnmounted as onUnmounted16, ref as ref11, shallowRef as shallowRef15 } from "vue";
74
4365
 
75
4366
  // src/client/actions.ts
76
4367
  var normalizeErrorMessage = (value) => {
@@ -120,6 +4411,11 @@ var serverMessageToAction = (message) => {
120
4411
  sessionId: message.sessionId,
121
4412
  type: "complete"
122
4413
  };
4414
+ case "connection":
4415
+ return {
4416
+ reconnect: message.reconnect,
4417
+ type: "connection"
4418
+ };
123
4419
  case "call_lifecycle":
124
4420
  return {
125
4421
  event: message.event,
@@ -141,6 +4437,17 @@ var serverMessageToAction = (message) => {
141
4437
  transcript: message.transcript,
142
4438
  type: "partial"
143
4439
  };
4440
+ case "replay":
4441
+ return {
4442
+ assistantTexts: message.assistantTexts,
4443
+ call: message.call,
4444
+ partial: message.partial,
4445
+ scenarioId: message.scenarioId,
4446
+ sessionId: message.sessionId,
4447
+ status: message.status,
4448
+ turns: message.turns,
4449
+ type: "replay"
4450
+ };
144
4451
  case "session":
145
4452
  return {
146
4453
  sessionId: message.sessionId,
@@ -201,10 +4508,12 @@ var isVoiceServerMessage = (value) => {
201
4508
  case "assistant":
202
4509
  case "call_lifecycle":
203
4510
  case "complete":
4511
+ case "connection":
204
4512
  case "error":
205
4513
  case "final":
206
4514
  case "partial":
207
4515
  case "pong":
4516
+ case "replay":
208
4517
  case "session":
209
4518
  case "turn":
210
4519
  return true;
@@ -241,6 +4550,9 @@ var createVoiceConnection = (path, options = {}) => {
241
4550
  sessionId: options.sessionId ?? createSessionId(),
242
4551
  ws: null
243
4552
  };
4553
+ const emitConnection = (reconnect) => {
4554
+ listeners.forEach((listener) => listener(reconnect));
4555
+ };
244
4556
  const clearTimers = () => {
245
4557
  if (state.pingInterval) {
246
4558
  clearInterval(state.pingInterval);
@@ -263,9 +4575,28 @@ var createVoiceConnection = (path, options = {}) => {
263
4575
  }
264
4576
  };
265
4577
  const scheduleReconnect = () => {
4578
+ const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
266
4579
  state.reconnectAttempts += 1;
4580
+ emitConnection({
4581
+ reconnect: {
4582
+ attempts: state.reconnectAttempts,
4583
+ lastDisconnectAt: Date.now(),
4584
+ maxAttempts: maxReconnectAttempts,
4585
+ nextAttemptAt,
4586
+ status: "reconnecting"
4587
+ },
4588
+ type: "connection"
4589
+ });
267
4590
  state.reconnectTimeout = setTimeout(() => {
268
4591
  if (state.reconnectAttempts > maxReconnectAttempts) {
4592
+ emitConnection({
4593
+ reconnect: {
4594
+ attempts: state.reconnectAttempts,
4595
+ maxAttempts: maxReconnectAttempts,
4596
+ status: "exhausted"
4597
+ },
4598
+ type: "connection"
4599
+ });
269
4600
  return;
270
4601
  }
271
4602
  connect();
@@ -275,9 +4606,21 @@ var createVoiceConnection = (path, options = {}) => {
275
4606
  const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
276
4607
  ws.binaryType = "arraybuffer";
277
4608
  ws.onopen = () => {
4609
+ const wasReconnecting = state.reconnectAttempts > 0;
278
4610
  state.isConnected = true;
279
- state.reconnectAttempts = 0;
280
4611
  flushPendingMessages();
4612
+ if (wasReconnecting) {
4613
+ emitConnection({
4614
+ reconnect: {
4615
+ attempts: state.reconnectAttempts,
4616
+ lastResumedAt: Date.now(),
4617
+ maxAttempts: maxReconnectAttempts,
4618
+ status: "resumed"
4619
+ },
4620
+ type: "connection"
4621
+ });
4622
+ state.reconnectAttempts = 0;
4623
+ }
281
4624
  listeners.forEach((listener) => listener({
282
4625
  scenarioId: state.scenarioId ?? undefined,
283
4626
  sessionId: state.sessionId,
@@ -307,6 +4650,16 @@ var createVoiceConnection = (path, options = {}) => {
307
4650
  const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
308
4651
  if (reconnectable) {
309
4652
  scheduleReconnect();
4653
+ } else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
4654
+ emitConnection({
4655
+ reconnect: {
4656
+ attempts: state.reconnectAttempts,
4657
+ lastDisconnectAt: Date.now(),
4658
+ maxAttempts: maxReconnectAttempts,
4659
+ status: "exhausted"
4660
+ },
4661
+ type: "connection"
4662
+ });
310
4663
  }
311
4664
  };
312
4665
  state.ws = ws;
@@ -377,6 +4730,11 @@ var createVoiceConnection = (path, options = {}) => {
377
4730
  };
378
4731
 
379
4732
  // src/client/store.ts
4733
+ var createInitialReconnectState = () => ({
4734
+ attempts: 0,
4735
+ maxAttempts: 0,
4736
+ status: "idle"
4737
+ });
380
4738
  var createInitialState = () => ({
381
4739
  assistantAudio: [],
382
4740
  assistantTexts: [],
@@ -385,6 +4743,7 @@ var createInitialState = () => ({
385
4743
  isConnected: false,
386
4744
  scenarioId: null,
387
4745
  partial: "",
4746
+ reconnect: createInitialReconnectState(),
388
4747
  sessionId: null,
389
4748
  status: "idle",
390
4749
  turns: []
@@ -441,7 +4800,19 @@ var createVoiceStreamStore = () => {
441
4800
  case "connected":
442
4801
  state = {
443
4802
  ...state,
444
- isConnected: true
4803
+ isConnected: true,
4804
+ reconnect: state.reconnect.status === "reconnecting" ? {
4805
+ ...state.reconnect,
4806
+ lastResumedAt: Date.now(),
4807
+ nextAttemptAt: undefined,
4808
+ status: "resumed"
4809
+ } : state.reconnect
4810
+ };
4811
+ break;
4812
+ case "connection":
4813
+ state = {
4814
+ ...state,
4815
+ reconnect: action.reconnect
445
4816
  };
446
4817
  break;
447
4818
  case "disconnected":
@@ -469,6 +4840,26 @@ var createVoiceStreamStore = () => {
469
4840
  partial: action.transcript.text
470
4841
  };
471
4842
  break;
4843
+ case "replay":
4844
+ state = {
4845
+ ...state,
4846
+ assistantTexts: [...action.assistantTexts],
4847
+ call: action.call ?? null,
4848
+ error: null,
4849
+ isConnected: action.status === "active",
4850
+ partial: action.partial,
4851
+ reconnect: state.reconnect.status === "reconnecting" ? {
4852
+ ...state.reconnect,
4853
+ lastResumedAt: Date.now(),
4854
+ nextAttemptAt: undefined,
4855
+ status: "resumed"
4856
+ } : state.reconnect,
4857
+ scenarioId: action.scenarioId ?? state.scenarioId,
4858
+ sessionId: action.sessionId,
4859
+ status: action.status,
4860
+ turns: [...action.turns]
4861
+ };
4862
+ break;
472
4863
  case "session":
473
4864
  state = {
474
4865
  ...state,
@@ -516,10 +4907,34 @@ var createVoiceStream = (path, options = {}) => {
516
4907
  const notify = () => {
517
4908
  subscribers.forEach((subscriber) => subscriber());
518
4909
  };
4910
+ const reportReconnect = () => {
4911
+ if (!options.reconnectReportPath || typeof fetch === "undefined") {
4912
+ return;
4913
+ }
4914
+ const snapshot = store.getSnapshot();
4915
+ const body = JSON.stringify({
4916
+ at: Date.now(),
4917
+ reconnect: snapshot.reconnect,
4918
+ scenarioId: snapshot.scenarioId,
4919
+ sessionId: connection.getSessionId(),
4920
+ turnIds: snapshot.turns.map((turn) => turn.id)
4921
+ });
4922
+ fetch(options.reconnectReportPath, {
4923
+ body,
4924
+ headers: {
4925
+ "Content-Type": "application/json"
4926
+ },
4927
+ keepalive: true,
4928
+ method: "POST"
4929
+ }).catch(() => {});
4930
+ };
519
4931
  const unsubscribeConnection = connection.subscribe((message) => {
520
4932
  const action = serverMessageToAction(message);
521
4933
  if (action) {
522
4934
  store.dispatch(action);
4935
+ if (message.type === "connection") {
4936
+ reportReconnect();
4937
+ }
523
4938
  notify();
524
4939
  }
525
4940
  });
@@ -555,6 +4970,9 @@ var createVoiceStream = (path, options = {}) => {
555
4970
  get partial() {
556
4971
  return store.getSnapshot().partial;
557
4972
  },
4973
+ get reconnect() {
4974
+ return store.getSnapshot().reconnect;
4975
+ },
558
4976
  get sessionId() {
559
4977
  return connection.getSessionId();
560
4978
  },
@@ -586,17 +5004,18 @@ var createVoiceStream = (path, options = {}) => {
586
5004
  };
587
5005
 
588
5006
  // src/vue/useVoiceStream.ts
589
- var useVoiceStream = (path, options = {}) => {
5007
+ function useVoiceStream(path, options = {}) {
590
5008
  const stream = createVoiceStream(path, options);
591
- const assistantAudio = shallowRef([]);
592
- const assistantTexts = shallowRef([]);
593
- const call = shallowRef(null);
594
- const error = ref(null);
595
- const isConnected = ref(false);
596
- const partial = ref("");
597
- const sessionId = ref(stream.sessionId);
598
- const status = ref(stream.status);
599
- const turns = shallowRef([]);
5009
+ const assistantAudio = shallowRef15([]);
5010
+ const assistantTexts = shallowRef15([]);
5011
+ const call = shallowRef15(null);
5012
+ const error = ref11(null);
5013
+ const isConnected = ref11(false);
5014
+ const partial = ref11("");
5015
+ const reconnect = shallowRef15(stream.reconnect);
5016
+ const sessionId = ref11(stream.sessionId);
5017
+ const status = ref11(stream.status);
5018
+ const turns = shallowRef15([]);
600
5019
  const sync = () => {
601
5020
  assistantAudio.value = [...stream.assistantAudio];
602
5021
  assistantTexts.value = [...stream.assistantTexts];
@@ -604,6 +5023,7 @@ var useVoiceStream = (path, options = {}) => {
604
5023
  error.value = stream.error;
605
5024
  isConnected.value = stream.isConnected;
606
5025
  partial.value = stream.partial;
5026
+ reconnect.value = stream.reconnect;
607
5027
  sessionId.value = stream.sessionId;
608
5028
  status.value = stream.status;
609
5029
  turns.value = [...stream.turns];
@@ -614,7 +5034,7 @@ var useVoiceStream = (path, options = {}) => {
614
5034
  unsubscribe();
615
5035
  stream.close();
616
5036
  };
617
- onUnmounted(destroy);
5037
+ onUnmounted16(destroy);
618
5038
  return {
619
5039
  assistantAudio,
620
5040
  assistantTexts,
@@ -625,14 +5045,15 @@ var useVoiceStream = (path, options = {}) => {
625
5045
  error,
626
5046
  isConnected,
627
5047
  partial,
5048
+ reconnect,
628
5049
  sendAudio: (audio) => stream.sendAudio(audio),
629
5050
  sessionId,
630
5051
  status,
631
5052
  turns
632
5053
  };
633
- };
5054
+ }
634
5055
  // src/vue/useVoiceController.ts
635
- import { onUnmounted as onUnmounted2, ref as ref2, shallowRef as shallowRef2 } from "vue";
5056
+ import { onUnmounted as onUnmounted17, ref as ref12, shallowRef as shallowRef16 } from "vue";
636
5057
 
637
5058
  // src/client/htmx.ts
638
5059
  var DEFAULT_EVENT_NAME = "voice-refresh";
@@ -1101,6 +5522,7 @@ var createInitialState2 = (stream) => ({
1101
5522
  isConnected: stream.isConnected,
1102
5523
  isRecording: false,
1103
5524
  partial: stream.partial,
5525
+ reconnect: stream.reconnect,
1104
5526
  recordingError: null,
1105
5527
  sessionId: stream.sessionId,
1106
5528
  scenarioId: stream.scenarioId,
@@ -1130,6 +5552,7 @@ var createVoiceController = (path, options = {}) => {
1130
5552
  error: stream.error,
1131
5553
  isConnected: stream.isConnected,
1132
5554
  partial: stream.partial,
5555
+ reconnect: stream.reconnect,
1133
5556
  sessionId: stream.sessionId,
1134
5557
  scenarioId: stream.scenarioId,
1135
5558
  status: stream.status,
@@ -1154,7 +5577,13 @@ var createVoiceController = (path, options = {}) => {
1154
5577
  capture = createMicrophoneCapture({
1155
5578
  channelCount: options.capture?.channelCount ?? preset.capture.channelCount,
1156
5579
  onLevel: options.capture?.onLevel,
1157
- onAudio: (audio) => stream.sendAudio(audio),
5580
+ onAudio: (audio) => {
5581
+ if (options.capture?.onAudio) {
5582
+ options.capture.onAudio(audio, stream.sendAudio);
5583
+ return;
5584
+ }
5585
+ stream.sendAudio(audio);
5586
+ },
1158
5587
  sampleRateHz: options.capture?.sampleRateHz ?? preset.capture.sampleRateHz
1159
5588
  });
1160
5589
  return capture;
@@ -1224,6 +5653,9 @@ var createVoiceController = (path, options = {}) => {
1224
5653
  get recordingError() {
1225
5654
  return state.recordingError;
1226
5655
  },
5656
+ get reconnect() {
5657
+ return state.reconnect;
5658
+ },
1227
5659
  sendAudio: (audio) => stream.sendAudio(audio),
1228
5660
  get sessionId() {
1229
5661
  return state.sessionId;
@@ -1265,18 +5697,19 @@ var createVoiceController = (path, options = {}) => {
1265
5697
  };
1266
5698
 
1267
5699
  // src/vue/useVoiceController.ts
1268
- var useVoiceController = (path, options = {}) => {
5700
+ function useVoiceController(path, options = {}) {
1269
5701
  const controller = createVoiceController(path, options);
1270
- const assistantAudio = shallowRef2([]);
1271
- const assistantTexts = shallowRef2([]);
1272
- const error = ref2(null);
1273
- const isConnected = ref2(false);
1274
- const isRecording = ref2(false);
1275
- const partial = ref2("");
1276
- const recordingError = ref2(null);
1277
- const sessionId = ref2(controller.sessionId);
1278
- const status = ref2(controller.status);
1279
- const turns = shallowRef2([]);
5702
+ const assistantAudio = shallowRef16([]);
5703
+ const assistantTexts = shallowRef16([]);
5704
+ const error = ref12(null);
5705
+ const isConnected = ref12(false);
5706
+ const isRecording = ref12(false);
5707
+ const partial = ref12("");
5708
+ const reconnect = shallowRef16(controller.reconnect);
5709
+ const recordingError = ref12(null);
5710
+ const sessionId = ref12(controller.sessionId);
5711
+ const status = ref12(controller.status);
5712
+ const turns = shallowRef16([]);
1280
5713
  const sync = () => {
1281
5714
  assistantAudio.value = [...controller.assistantAudio];
1282
5715
  assistantTexts.value = [...controller.assistantTexts];
@@ -1284,6 +5717,7 @@ var useVoiceController = (path, options = {}) => {
1284
5717
  isConnected.value = controller.isConnected;
1285
5718
  isRecording.value = controller.isRecording;
1286
5719
  partial.value = controller.partial;
5720
+ reconnect.value = controller.reconnect;
1287
5721
  recordingError.value = controller.recordingError;
1288
5722
  sessionId.value = controller.sessionId;
1289
5723
  status.value = controller.status;
@@ -1295,7 +5729,7 @@ var useVoiceController = (path, options = {}) => {
1295
5729
  unsubscribe();
1296
5730
  controller.close();
1297
5731
  };
1298
- onUnmounted2(destroy);
5732
+ onUnmounted17(destroy);
1299
5733
  return {
1300
5734
  assistantAudio,
1301
5735
  assistantTexts,
@@ -1306,6 +5740,7 @@ var useVoiceController = (path, options = {}) => {
1306
5740
  isConnected,
1307
5741
  isRecording,
1308
5742
  partial,
5743
+ reconnect,
1309
5744
  recordingError,
1310
5745
  sendAudio: (audio) => controller.sendAudio(audio),
1311
5746
  sessionId,
@@ -1315,27 +5750,56 @@ var useVoiceController = (path, options = {}) => {
1315
5750
  toggleRecording: () => controller.toggleRecording(),
1316
5751
  turns
1317
5752
  };
1318
- };
1319
- // src/vue/useVoiceProviderStatus.ts
1320
- import { onUnmounted as onUnmounted3, ref as ref3, shallowRef as shallowRef3 } from "vue";
5753
+ }
5754
+ // src/vue/useVoiceTraceTimeline.ts
5755
+ import { onUnmounted as onUnmounted18, ref as ref13, shallowRef as shallowRef17 } from "vue";
5756
+ function useVoiceTraceTimeline(path = "/api/voice-traces", options = {}) {
5757
+ const store = createVoiceTraceTimelineStore(path, options);
5758
+ const error = ref13(null);
5759
+ const isLoading = ref13(false);
5760
+ const report = shallowRef17(null);
5761
+ const updatedAt = ref13(undefined);
5762
+ const sync = () => {
5763
+ const snapshot = store.getSnapshot();
5764
+ error.value = snapshot.error;
5765
+ isLoading.value = snapshot.isLoading;
5766
+ report.value = snapshot.report;
5767
+ updatedAt.value = snapshot.updatedAt;
5768
+ };
5769
+ const unsubscribe = store.subscribe(sync);
5770
+ sync();
5771
+ store.refresh().catch(() => {});
5772
+ onUnmounted18(() => {
5773
+ unsubscribe();
5774
+ store.close();
5775
+ });
5776
+ return {
5777
+ error,
5778
+ isLoading,
5779
+ refresh: store.refresh,
5780
+ report,
5781
+ updatedAt
5782
+ };
5783
+ }
5784
+ // src/vue/useVoiceWorkflowStatus.ts
5785
+ import { onUnmounted as onUnmounted19, ref as ref14, shallowRef as shallowRef18 } from "vue";
1321
5786
 
1322
- // src/client/providerStatus.ts
1323
- var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
5787
+ // src/client/workflowStatus.ts
5788
+ var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
1324
5789
  const fetchImpl = options.fetch ?? globalThis.fetch;
1325
5790
  const response = await fetchImpl(path);
1326
5791
  if (!response.ok) {
1327
- throw new Error(`Voice provider status failed: HTTP ${response.status}`);
5792
+ throw new Error(`Voice workflow status failed: HTTP ${response.status}`);
1328
5793
  }
1329
5794
  return await response.json();
1330
5795
  };
1331
- var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
5796
+ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options = {}) => {
1332
5797
  const listeners = new Set;
1333
5798
  let closed = false;
1334
5799
  let timer;
1335
5800
  let snapshot = {
1336
5801
  error: null,
1337
- isLoading: false,
1338
- providers: []
5802
+ isLoading: false
1339
5803
  };
1340
5804
  const emit = () => {
1341
5805
  for (const listener of listeners) {
@@ -1344,7 +5808,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1344
5808
  };
1345
5809
  const refresh = async () => {
1346
5810
  if (closed) {
1347
- return snapshot.providers;
5811
+ return snapshot.report;
1348
5812
  }
1349
5813
  snapshot = {
1350
5814
  ...snapshot,
@@ -1353,15 +5817,15 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1353
5817
  };
1354
5818
  emit();
1355
5819
  try {
1356
- const providers = await fetchVoiceProviderStatus(path, options);
5820
+ const report = await fetchVoiceWorkflowStatus(path, options);
1357
5821
  snapshot = {
1358
5822
  error: null,
1359
5823
  isLoading: false,
1360
- providers,
5824
+ report,
1361
5825
  updatedAt: Date.now()
1362
5826
  };
1363
5827
  emit();
1364
- return providers;
5828
+ return report;
1365
5829
  } catch (error) {
1366
5830
  snapshot = {
1367
5831
  ...snapshot,
@@ -1380,7 +5844,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1380
5844
  }
1381
5845
  listeners.clear();
1382
5846
  };
1383
- if (options.intervalMs && options.intervalMs > 0) {
5847
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
1384
5848
  timer = setInterval(() => {
1385
5849
  refresh().catch(() => {});
1386
5850
  }, options.intervalMs);
@@ -1399,37 +5863,67 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1399
5863
  };
1400
5864
  };
1401
5865
 
1402
- // src/vue/useVoiceProviderStatus.ts
1403
- var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
1404
- const store = createVoiceProviderStatusStore(path, options);
1405
- const error = ref3(null);
1406
- const isLoading = ref3(false);
1407
- const providers = shallowRef3([]);
1408
- const updatedAt = ref3(undefined);
5866
+ // src/vue/useVoiceWorkflowStatus.ts
5867
+ function useVoiceWorkflowStatus(path = "/evals/scenarios/json", options = {}) {
5868
+ const store = createVoiceWorkflowStatusStore(path, options);
5869
+ const error = ref14(null);
5870
+ const isLoading = ref14(false);
5871
+ const report = shallowRef18(undefined);
5872
+ const updatedAt = ref14(undefined);
1409
5873
  const sync = () => {
1410
5874
  const snapshot = store.getSnapshot();
1411
5875
  error.value = snapshot.error;
1412
5876
  isLoading.value = snapshot.isLoading;
1413
- providers.value = [...snapshot.providers];
5877
+ report.value = snapshot.report;
1414
5878
  updatedAt.value = snapshot.updatedAt;
1415
5879
  };
1416
5880
  const unsubscribe = store.subscribe(sync);
1417
5881
  sync();
1418
- store.refresh().catch(() => {});
1419
- onUnmounted3(() => {
5882
+ if (typeof window !== "undefined") {
5883
+ store.refresh().catch(() => {});
5884
+ }
5885
+ onUnmounted19(() => {
1420
5886
  unsubscribe();
1421
5887
  store.close();
1422
5888
  });
1423
5889
  return {
1424
5890
  error,
1425
5891
  isLoading,
1426
- providers,
1427
5892
  refresh: store.refresh,
5893
+ report,
1428
5894
  updatedAt
1429
5895
  };
1430
- };
5896
+ }
1431
5897
  export {
5898
+ useVoiceWorkflowStatus,
5899
+ useVoiceTurnQuality,
5900
+ useVoiceTurnLatency,
5901
+ useVoiceTraceTimeline,
1432
5902
  useVoiceStream,
5903
+ useVoiceRoutingStatus,
1433
5904
  useVoiceProviderStatus,
1434
- useVoiceController
5905
+ useVoiceProviderSimulationControls,
5906
+ useVoiceProviderContracts,
5907
+ useVoiceProviderCapabilities,
5908
+ useVoiceProofTrends,
5909
+ useVoicePlatformCoverage,
5910
+ useVoiceOpsStatus,
5911
+ useVoiceOpsActionCenter,
5912
+ useVoiceLiveOps,
5913
+ useVoiceDeliveryRuntime,
5914
+ useVoiceController,
5915
+ useVoiceCampaignDialerProof,
5916
+ useVoiceAgentSquadStatus,
5917
+ VoiceTurnQuality,
5918
+ VoiceTurnLatency,
5919
+ VoiceRoutingStatus,
5920
+ VoiceProviderStatus,
5921
+ VoiceProviderSimulationControls,
5922
+ VoiceProviderContracts,
5923
+ VoiceProviderCapabilities,
5924
+ VoiceProofTrends,
5925
+ VoicePlatformCoverage,
5926
+ VoiceOpsStatus,
5927
+ VoiceOpsActionCenter,
5928
+ VoiceDeliveryRuntime
1435
5929
  };