@absolutejs/voice 0.0.22-beta.25 → 0.0.22-beta.251

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 (226) hide show
  1. package/README.md +3234 -228
  2. package/dist/agent.d.ts +62 -0
  3. package/dist/agentSquadContract.d.ts +69 -0
  4. package/dist/angular/index.d.ts +15 -0
  5. package/dist/angular/index.js +3387 -1093
  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 +3 -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 +746 -0
  32. package/dist/campaignDialers.d.ts +90 -0
  33. package/dist/client/actions.d.ts +105 -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/connection.d.ts +3 -0
  39. package/dist/client/deliveryRuntime.d.ts +34 -0
  40. package/dist/client/deliveryRuntimeWidget.d.ts +37 -0
  41. package/dist/client/duplex.d.ts +1 -1
  42. package/dist/client/htmxBootstrap.js +747 -15
  43. package/dist/client/index.d.ts +66 -0
  44. package/dist/client/index.js +4894 -21
  45. package/dist/client/liveOps.d.ts +22 -0
  46. package/dist/client/liveOpsWidget.d.ts +23 -0
  47. package/dist/client/liveTurnLatency.d.ts +41 -0
  48. package/dist/client/opsActionCenter.d.ts +54 -0
  49. package/dist/client/opsActionCenterWidget.d.ts +29 -0
  50. package/dist/client/opsActionHistory.d.ts +19 -0
  51. package/dist/client/opsActionHistoryWidget.d.ts +11 -0
  52. package/dist/client/opsStatus.d.ts +19 -0
  53. package/dist/client/opsStatusWidget.d.ts +40 -0
  54. package/dist/client/platformCoverage.d.ts +19 -0
  55. package/dist/client/platformCoverageWidget.d.ts +37 -0
  56. package/dist/client/proofTrends.d.ts +19 -0
  57. package/dist/client/proofTrendsWidget.d.ts +37 -0
  58. package/dist/client/providerCapabilities.d.ts +19 -0
  59. package/dist/client/providerCapabilitiesWidget.d.ts +32 -0
  60. package/dist/client/providerContracts.d.ts +19 -0
  61. package/dist/client/providerContractsWidget.d.ts +37 -0
  62. package/dist/client/providerSimulationControls.d.ts +33 -0
  63. package/dist/client/providerSimulationControlsWidget.d.ts +20 -0
  64. package/dist/client/providerStatusWidget.d.ts +32 -0
  65. package/dist/client/routingStatus.d.ts +19 -0
  66. package/dist/client/routingStatusWidget.d.ts +28 -0
  67. package/dist/client/traceTimeline.d.ts +19 -0
  68. package/dist/client/traceTimelineWidget.d.ts +36 -0
  69. package/dist/client/turnLatency.d.ts +22 -0
  70. package/dist/client/turnLatencyWidget.d.ts +33 -0
  71. package/dist/client/turnQuality.d.ts +19 -0
  72. package/dist/client/turnQualityWidget.d.ts +32 -0
  73. package/dist/client/workflowStatus.d.ts +19 -0
  74. package/dist/dataControl.d.ts +140 -0
  75. package/dist/deliveryRuntime.d.ts +158 -0
  76. package/dist/deliverySinkRoutes.d.ts +117 -0
  77. package/dist/demoReadyRoutes.d.ts +98 -0
  78. package/dist/diagnosticsRoutes.d.ts +44 -0
  79. package/dist/evalRoutes.d.ts +219 -0
  80. package/dist/fileStore.d.ts +14 -2
  81. package/dist/guardrails.d.ts +128 -0
  82. package/dist/handoff.d.ts +54 -0
  83. package/dist/handoffHealth.d.ts +94 -0
  84. package/dist/incidentBundle.d.ts +116 -0
  85. package/dist/index.d.ts +132 -13
  86. package/dist/index.js +24299 -4986
  87. package/dist/latencySlo.d.ts +56 -0
  88. package/dist/liveLatency.d.ts +78 -0
  89. package/dist/liveOps.d.ts +122 -0
  90. package/dist/modelAdapters.d.ts +23 -2
  91. package/dist/observabilityExport.d.ts +428 -0
  92. package/dist/openaiRealtime.d.ts +27 -0
  93. package/dist/openaiTTS.d.ts +18 -0
  94. package/dist/operationsRecord.d.ts +210 -0
  95. package/dist/opsActionAuditRoutes.d.ts +99 -0
  96. package/dist/opsConsoleRoutes.d.ts +80 -0
  97. package/dist/opsRecovery.d.ts +137 -0
  98. package/dist/opsStatus.d.ts +76 -0
  99. package/dist/opsStatusRoutes.d.ts +33 -0
  100. package/dist/outcomeContract.d.ts +115 -0
  101. package/dist/phoneAgent.d.ts +76 -0
  102. package/dist/phoneAgentProductionSmoke.d.ts +115 -0
  103. package/dist/platformCoverage.d.ts +91 -0
  104. package/dist/postCallAnalysis.d.ts +98 -0
  105. package/dist/postgresStore.d.ts +13 -2
  106. package/dist/productionReadiness.d.ts +484 -0
  107. package/dist/proofTrends.d.ts +108 -0
  108. package/dist/providerAdapters.d.ts +48 -0
  109. package/dist/providerCapabilities.d.ts +92 -0
  110. package/dist/providerHealth.d.ts +1 -0
  111. package/dist/providerRoutingContract.d.ts +38 -0
  112. package/dist/providerSlo.d.ts +142 -0
  113. package/dist/providerStackRecommendations.d.ts +145 -0
  114. package/dist/qualityRoutes.d.ts +76 -0
  115. package/dist/queue.d.ts +61 -0
  116. package/dist/react/VoiceAgentSquadStatus.d.ts +5 -0
  117. package/dist/react/VoiceDeliveryRuntime.d.ts +7 -0
  118. package/dist/react/VoiceOpsActionCenter.d.ts +5 -0
  119. package/dist/react/VoiceOpsStatus.d.ts +6 -0
  120. package/dist/react/VoicePlatformCoverage.d.ts +6 -0
  121. package/dist/react/VoiceProofTrends.d.ts +6 -0
  122. package/dist/react/VoiceProviderCapabilities.d.ts +6 -0
  123. package/dist/react/VoiceProviderContracts.d.ts +6 -0
  124. package/dist/react/VoiceProviderSimulationControls.d.ts +5 -0
  125. package/dist/react/VoiceProviderStatus.d.ts +6 -0
  126. package/dist/react/VoiceRoutingStatus.d.ts +6 -0
  127. package/dist/react/VoiceTraceTimeline.d.ts +6 -0
  128. package/dist/react/VoiceTurnLatency.d.ts +6 -0
  129. package/dist/react/VoiceTurnQuality.d.ts +6 -0
  130. package/dist/react/index.d.ts +30 -0
  131. package/dist/react/index.js +4739 -33
  132. package/dist/react/useVoiceAgentSquadStatus.d.ts +8 -0
  133. package/dist/react/useVoiceCampaignDialerProof.d.ts +10 -0
  134. package/dist/react/useVoiceController.d.ts +3 -0
  135. package/dist/react/useVoiceDeliveryRuntime.d.ts +13 -0
  136. package/dist/react/useVoiceLiveOps.d.ts +9 -0
  137. package/dist/react/useVoiceOpsActionCenter.d.ts +11 -0
  138. package/dist/react/useVoiceOpsStatus.d.ts +8 -0
  139. package/dist/react/useVoicePlatformCoverage.d.ts +8 -0
  140. package/dist/react/useVoiceProofTrends.d.ts +8 -0
  141. package/dist/react/useVoiceProviderCapabilities.d.ts +8 -0
  142. package/dist/react/useVoiceProviderContracts.d.ts +8 -0
  143. package/dist/react/useVoiceProviderSimulationControls.d.ts +10 -0
  144. package/dist/react/useVoiceRoutingStatus.d.ts +8 -0
  145. package/dist/react/useVoiceStream.d.ts +3 -0
  146. package/dist/react/useVoiceTraceTimeline.d.ts +8 -0
  147. package/dist/react/useVoiceTurnLatency.d.ts +9 -0
  148. package/dist/react/useVoiceTurnQuality.d.ts +8 -0
  149. package/dist/react/useVoiceWorkflowStatus.d.ts +8 -0
  150. package/dist/readinessProfiles.d.ts +37 -0
  151. package/dist/reconnectContract.d.ts +87 -0
  152. package/dist/resilienceRoutes.d.ts +143 -0
  153. package/dist/sessionReplay.d.ts +12 -0
  154. package/dist/simulationSuite.d.ts +121 -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 +4754 -439
  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 +254 -0
  178. package/dist/telephony/telnyx.d.ts +247 -0
  179. package/dist/telephony/twilio.d.ts +135 -2
  180. package/dist/telephonyOutcome.d.ts +201 -0
  181. package/dist/testing/index.d.ts +1 -0
  182. package/dist/testing/index.js +2024 -69
  183. package/dist/testing/ioProviderSimulator.d.ts +41 -0
  184. package/dist/toolContract.d.ts +133 -0
  185. package/dist/toolRuntime.d.ts +50 -0
  186. package/dist/trace.d.ts +19 -1
  187. package/dist/traceDeliveryRoutes.d.ts +86 -0
  188. package/dist/traceTimeline.d.ts +97 -0
  189. package/dist/turnLatency.d.ts +95 -0
  190. package/dist/turnQuality.d.ts +94 -0
  191. package/dist/types.d.ts +180 -4
  192. package/dist/vue/VoiceDeliveryRuntime.d.ts +30 -0
  193. package/dist/vue/VoiceOpsActionCenter.d.ts +13 -0
  194. package/dist/vue/VoiceOpsStatus.d.ts +30 -0
  195. package/dist/vue/VoicePlatformCoverage.d.ts +23 -0
  196. package/dist/vue/VoiceProofTrends.d.ts +21 -0
  197. package/dist/vue/VoiceProviderCapabilities.d.ts +51 -0
  198. package/dist/vue/VoiceProviderContracts.d.ts +21 -0
  199. package/dist/vue/VoiceProviderSimulationControls.d.ts +88 -0
  200. package/dist/vue/VoiceProviderStatus.d.ts +51 -0
  201. package/dist/vue/VoiceRoutingStatus.d.ts +51 -0
  202. package/dist/vue/VoiceTurnLatency.d.ts +69 -0
  203. package/dist/vue/VoiceTurnQuality.d.ts +51 -0
  204. package/dist/vue/index.d.ts +28 -0
  205. package/dist/vue/index.js +4519 -57
  206. package/dist/vue/useVoiceAgentSquadStatus.d.ts +9 -0
  207. package/dist/vue/useVoiceCampaignDialerProof.d.ts +11 -0
  208. package/dist/vue/useVoiceController.d.ts +2 -1
  209. package/dist/vue/useVoiceDeliveryRuntime.d.ts +13 -0
  210. package/dist/vue/useVoiceLiveOps.d.ts +9 -0
  211. package/dist/vue/useVoiceOpsActionCenter.d.ts +11 -0
  212. package/dist/vue/useVoiceOpsStatus.d.ts +9 -0
  213. package/dist/vue/useVoicePlatformCoverage.d.ts +9 -0
  214. package/dist/vue/useVoiceProofTrends.d.ts +9 -0
  215. package/dist/vue/useVoiceProviderCapabilities.d.ts +9 -0
  216. package/dist/vue/useVoiceProviderContracts.d.ts +9 -0
  217. package/dist/vue/useVoiceProviderSimulationControls.d.ts +24 -0
  218. package/dist/vue/useVoiceProviderStatus.d.ts +1 -1
  219. package/dist/vue/useVoiceRoutingStatus.d.ts +8 -0
  220. package/dist/vue/useVoiceStream.d.ts +4 -1
  221. package/dist/vue/useVoiceTraceTimeline.d.ts +9 -0
  222. package/dist/vue/useVoiceTurnLatency.d.ts +10 -0
  223. package/dist/vue/useVoiceTurnQuality.d.ts +9 -0
  224. package/dist/vue/useVoiceWorkflowStatus.d.ts +9 -0
  225. package/dist/workflowContract.d.ts +91 -0
  226. package/package.json +1 -1
package/dist/vue/index.js CHANGED
@@ -69,8 +69,4221 @@ 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 createVoiceProofTrendRoutes = (options) => {
1489
+ const path = options.path ?? "/api/voice/proof-trends";
1490
+ const routes = new Elysia({
1491
+ name: options.name ?? "absolutejs-voice-proof-trends"
1492
+ });
1493
+ routes.get(path, async () => {
1494
+ const value = options.source !== undefined ? typeof options.source === "function" ? await options.source() : options.source : options.jsonPath ? await readVoiceProofTrendReportFile(options.jsonPath, {
1495
+ maxAgeMs: options.maxAgeMs
1496
+ }) : buildEmptyVoiceProofTrendReport("", options.maxAgeMs);
1497
+ return Response.json(normalizeVoiceProofTrendReport(value, {
1498
+ maxAgeMs: options.maxAgeMs,
1499
+ source: options.jsonPath
1500
+ }), { headers: options.headers });
1501
+ });
1502
+ return routes;
1503
+ };
1504
+ var formatVoiceProofTrendAge = (ageMs) => {
1505
+ if (typeof ageMs !== "number" || !Number.isFinite(ageMs)) {
1506
+ return "unknown";
1507
+ }
1508
+ const minutes = Math.floor(ageMs / 60000);
1509
+ if (minutes < 1) {
1510
+ return "less than 1m";
1511
+ }
1512
+ if (minutes < 60) {
1513
+ return `${minutes}m`;
1514
+ }
1515
+ const hours = Math.floor(minutes / 60);
1516
+ if (hours < 48) {
1517
+ return `${hours}h ${minutes % 60}m`;
1518
+ }
1519
+ const days = Math.floor(hours / 24);
1520
+ return `${days}d ${hours % 24}h`;
1521
+ };
1522
+
1523
+ // src/client/proofTrends.ts
1524
+ var fetchVoiceProofTrends = async (path = "/api/voice/proof-trends", options = {}) => {
1525
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1526
+ const response = await fetchImpl(path);
1527
+ if (!response.ok) {
1528
+ throw new Error(`Voice proof trends failed: HTTP ${response.status}`);
1529
+ }
1530
+ return await response.json();
1531
+ };
1532
+ var createVoiceProofTrendsStore = (path = "/api/voice/proof-trends", options = {}) => {
1533
+ const listeners = new Set;
1534
+ let closed = false;
1535
+ let timer;
1536
+ let snapshot = {
1537
+ error: null,
1538
+ isLoading: false
1539
+ };
1540
+ const emit = () => {
1541
+ for (const listener of listeners) {
1542
+ listener();
1543
+ }
1544
+ };
1545
+ const refresh = async () => {
1546
+ if (closed) {
1547
+ return snapshot.report;
1548
+ }
1549
+ snapshot = {
1550
+ ...snapshot,
1551
+ error: null,
1552
+ isLoading: true
1553
+ };
1554
+ emit();
1555
+ try {
1556
+ const report = await fetchVoiceProofTrends(path, options);
1557
+ snapshot = {
1558
+ error: null,
1559
+ isLoading: false,
1560
+ report,
1561
+ updatedAt: Date.now()
1562
+ };
1563
+ emit();
1564
+ return report;
1565
+ } catch (error) {
1566
+ snapshot = {
1567
+ ...snapshot,
1568
+ error: error instanceof Error ? error.message : String(error),
1569
+ isLoading: false
1570
+ };
1571
+ emit();
1572
+ throw error;
1573
+ }
1574
+ };
1575
+ const close = () => {
1576
+ closed = true;
1577
+ if (timer) {
1578
+ clearInterval(timer);
1579
+ timer = undefined;
1580
+ }
1581
+ listeners.clear();
1582
+ };
1583
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
1584
+ timer = setInterval(() => {
1585
+ refresh().catch(() => {});
1586
+ }, options.intervalMs);
1587
+ }
1588
+ return {
1589
+ close,
1590
+ getServerSnapshot: () => snapshot,
1591
+ getSnapshot: () => snapshot,
1592
+ refresh,
1593
+ subscribe: (listener) => {
1594
+ listeners.add(listener);
1595
+ return () => {
1596
+ listeners.delete(listener);
1597
+ };
1598
+ }
1599
+ };
1600
+ };
1601
+
1602
+ // src/client/proofTrendsWidget.ts
1603
+ var DEFAULT_TITLE5 = "Sustained Proof Trends";
1604
+ var DEFAULT_DESCRIPTION5 = "Repeated-cycle provider, latency, recovery, and readiness evidence with freshness gating.";
1605
+ var DEFAULT_LINKS2 = [
1606
+ { href: "/voice/proof-trends", label: "Trend page" },
1607
+ { href: "/api/voice/proof-trends", label: "Trend JSON" }
1608
+ ];
1609
+ var escapeHtml5 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1610
+ var formatMs = (value) => typeof value === "number" && Number.isFinite(value) ? `${Math.round(value)}ms` : "n/a";
1611
+ var statusLabel = (report) => {
1612
+ if (!report) {
1613
+ return "No trend report";
1614
+ }
1615
+ if (report.status === "pass") {
1616
+ return `${report.summary.cycles ?? report.cycles.length} cycles passing`;
1617
+ }
1618
+ return report.status;
1619
+ };
1620
+ var createVoiceProofTrendsViewModel = (snapshot, options = {}) => {
1621
+ const report = snapshot.report;
1622
+ const metrics = report ? [
1623
+ { label: "Status", value: report.status.toUpperCase() },
1624
+ {
1625
+ label: "Cycles",
1626
+ value: String(report.summary.cycles ?? report.cycles.length)
1627
+ },
1628
+ {
1629
+ label: "Provider p95",
1630
+ value: formatMs(report.summary.maxProviderP95Ms)
1631
+ },
1632
+ { label: "Turn p95", value: formatMs(report.summary.maxTurnP95Ms) },
1633
+ { label: "Live p95", value: formatMs(report.summary.maxLiveP95Ms) },
1634
+ {
1635
+ label: "Artifact age",
1636
+ value: formatVoiceProofTrendAge(report.ageMs)
1637
+ },
1638
+ {
1639
+ label: "Stale after",
1640
+ value: formatVoiceProofTrendAge(report.maxAgeMs)
1641
+ }
1642
+ ] : [];
1643
+ return {
1644
+ description: options.description ?? DEFAULT_DESCRIPTION5,
1645
+ error: snapshot.error,
1646
+ isLoading: snapshot.isLoading,
1647
+ label: snapshot.error ? "Unavailable" : report ? statusLabel(report) : snapshot.isLoading ? "Checking" : "No trend report",
1648
+ links: options.links ?? DEFAULT_LINKS2,
1649
+ metrics,
1650
+ report,
1651
+ status: snapshot.error ? "error" : report ? report.status === "pass" ? "ready" : "warning" : snapshot.isLoading ? "loading" : "empty",
1652
+ title: options.title ?? DEFAULT_TITLE5,
1653
+ updatedAt: snapshot.updatedAt
1654
+ };
1655
+ };
1656
+ var renderVoiceProofTrendsHTML = (snapshot, options = {}) => {
1657
+ const model = createVoiceProofTrendsViewModel(snapshot, options);
1658
+ const metrics = model.metrics.length ? `<div class="absolute-voice-proof-trends__metrics">${model.metrics.map((metric) => `<article>
1659
+ <span>${escapeHtml5(metric.label)}</span>
1660
+ <strong>${escapeHtml5(metric.value)}</strong>
1661
+ </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>`;
1662
+ 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>` : "";
1663
+ return `<section class="absolute-voice-proof-trends absolute-voice-proof-trends--${escapeHtml5(model.status)}">
1664
+ <header class="absolute-voice-proof-trends__header">
1665
+ <span class="absolute-voice-proof-trends__eyebrow">${escapeHtml5(model.title)}</span>
1666
+ <strong class="absolute-voice-proof-trends__label">${escapeHtml5(model.label)}</strong>
1667
+ </header>
1668
+ <p class="absolute-voice-proof-trends__description">${escapeHtml5(model.description)}</p>
1669
+ ${metrics}
1670
+ ${links}
1671
+ ${model.error ? `<p class="absolute-voice-proof-trends__error">${escapeHtml5(model.error)}</p>` : ""}
1672
+ </section>`;
1673
+ };
1674
+ 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}`;
1675
+ var mountVoiceProofTrends = (element, path = "/api/voice/proof-trends", options = {}) => {
1676
+ const store = createVoiceProofTrendsStore(path, options);
1677
+ const render = () => {
1678
+ element.innerHTML = renderVoiceProofTrendsHTML(store.getSnapshot(), options);
1679
+ };
1680
+ const unsubscribe = store.subscribe(render);
1681
+ render();
1682
+ store.refresh().catch(() => {});
1683
+ return {
1684
+ close: () => {
1685
+ unsubscribe();
1686
+ store.close();
1687
+ },
1688
+ refresh: store.refresh
1689
+ };
1690
+ };
1691
+ var defineVoiceProofTrendsElement = (tagName = "absolute-voice-proof-trends") => {
1692
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
1693
+ return;
1694
+ }
1695
+ customElements.define(tagName, class AbsoluteVoiceProofTrendsElement extends HTMLElement {
1696
+ mounted;
1697
+ connectedCallback() {
1698
+ this.mounted = mountVoiceProofTrends(this, this.getAttribute("path") ?? "/api/voice/proof-trends", {
1699
+ description: this.getAttribute("description") ?? undefined,
1700
+ intervalMs: Number(this.getAttribute("interval-ms") ?? 0) || undefined,
1701
+ title: this.getAttribute("title") ?? undefined
1702
+ });
1703
+ }
1704
+ disconnectedCallback() {
1705
+ this.mounted?.close();
1706
+ this.mounted = undefined;
1707
+ }
1708
+ });
1709
+ };
1710
+
1711
+ // src/vue/useVoiceProofTrends.ts
1712
+ import { onUnmounted as onUnmounted5, ref as ref5, shallowRef as shallowRef5 } from "vue";
1713
+ function useVoiceProofTrends(path = "/api/voice/proof-trends", options = {}) {
1714
+ const store = createVoiceProofTrendsStore(path, options);
1715
+ const error = ref5(null);
1716
+ const isLoading = ref5(false);
1717
+ const report = shallowRef5(undefined);
1718
+ const updatedAt = ref5(undefined);
1719
+ const sync = () => {
1720
+ const snapshot = store.getSnapshot();
1721
+ error.value = snapshot.error;
1722
+ isLoading.value = snapshot.isLoading;
1723
+ report.value = snapshot.report;
1724
+ updatedAt.value = snapshot.updatedAt;
1725
+ };
1726
+ const unsubscribe = store.subscribe(sync);
1727
+ sync();
1728
+ if (typeof window !== "undefined") {
1729
+ store.refresh().catch(() => {});
1730
+ }
1731
+ onUnmounted5(() => {
1732
+ unsubscribe();
1733
+ store.close();
1734
+ });
1735
+ return {
1736
+ error,
1737
+ isLoading,
1738
+ refresh: store.refresh,
1739
+ report,
1740
+ updatedAt
1741
+ };
1742
+ }
1743
+
1744
+ // src/vue/VoiceProofTrends.ts
1745
+ var VoiceProofTrends = defineComponent5({
1746
+ name: "VoiceProofTrends",
1747
+ props: {
1748
+ description: String,
1749
+ intervalMs: Number,
1750
+ path: {
1751
+ default: "/api/voice/proof-trends",
1752
+ type: String
1753
+ },
1754
+ title: String
1755
+ },
1756
+ setup(props) {
1757
+ const state = useVoiceProofTrends(props.path, {
1758
+ description: props.description,
1759
+ intervalMs: props.intervalMs,
1760
+ title: props.title
1761
+ });
1762
+ return () => {
1763
+ const model = createVoiceProofTrendsViewModel({
1764
+ error: state.error.value,
1765
+ isLoading: state.isLoading.value,
1766
+ report: state.report.value,
1767
+ updatedAt: state.updatedAt.value
1768
+ }, {
1769
+ description: props.description,
1770
+ intervalMs: props.intervalMs,
1771
+ title: props.title
1772
+ });
1773
+ return h5("section", {
1774
+ class: [
1775
+ "absolute-voice-proof-trends",
1776
+ `absolute-voice-proof-trends--${model.status}`
1777
+ ]
1778
+ }, [
1779
+ h5("header", { class: "absolute-voice-proof-trends__header" }, [
1780
+ h5("span", { class: "absolute-voice-proof-trends__eyebrow" }, model.title),
1781
+ h5("strong", { class: "absolute-voice-proof-trends__label" }, model.label)
1782
+ ]),
1783
+ h5("p", { class: "absolute-voice-proof-trends__description" }, model.description),
1784
+ model.metrics.length ? h5("div", { class: "absolute-voice-proof-trends__metrics" }, model.metrics.map((metric) => h5("article", { key: metric.label }, [
1785
+ h5("span", metric.label),
1786
+ h5("strong", metric.value)
1787
+ ]))) : h5("p", { class: "absolute-voice-proof-trends__empty" }, model.error ?? "Run the sustained proof trends script to populate evidence."),
1788
+ 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,
1789
+ model.error ? h5("p", { class: "absolute-voice-proof-trends__error" }, model.error) : null
1790
+ ]);
1791
+ };
1792
+ }
1793
+ });
1794
+ // src/vue/VoiceProviderSimulationControls.ts
1795
+ import { computed, defineComponent as defineComponent6, h as h6 } from "vue";
1796
+
1797
+ // src/client/providerSimulationControls.ts
1798
+ var postSimulation = async (pathPrefix, mode, provider, fetchImpl) => {
1799
+ const response = await fetchImpl(`${pathPrefix}/${mode}?provider=${encodeURIComponent(provider)}`, { method: "POST" });
1800
+ const body = await response.json().catch(() => null);
1801
+ if (!response.ok) {
1802
+ const message = body && typeof body === "object" && "error" in body ? String(body.error) : `Voice provider simulation failed: HTTP ${response.status}`;
1803
+ throw new Error(message);
1804
+ }
1805
+ return body;
1806
+ };
1807
+ var createVoiceProviderSimulationControlsStore = (options) => {
1808
+ const listeners = new Set;
1809
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1810
+ const pathPrefix = options.pathPrefix ?? `/api/${options.kind ?? "stt"}-simulate`;
1811
+ let closed = false;
1812
+ let snapshot = {
1813
+ error: null,
1814
+ isRunning: false,
1815
+ lastResult: null,
1816
+ mode: null,
1817
+ provider: null
1818
+ };
1819
+ const emit = () => {
1820
+ for (const listener of listeners) {
1821
+ listener();
1822
+ }
1823
+ };
1824
+ const run = async (provider, mode) => {
1825
+ if (closed) {
1826
+ return snapshot.lastResult;
1827
+ }
1828
+ snapshot = {
1829
+ ...snapshot,
1830
+ error: null,
1831
+ isRunning: true,
1832
+ mode,
1833
+ provider
1834
+ };
1835
+ emit();
1836
+ try {
1837
+ const result = await postSimulation(pathPrefix, mode, provider, fetchImpl);
1838
+ snapshot = {
1839
+ error: null,
1840
+ isRunning: false,
1841
+ lastResult: result,
1842
+ mode,
1843
+ provider,
1844
+ updatedAt: Date.now()
1845
+ };
1846
+ emit();
1847
+ return result;
1848
+ } catch (error) {
1849
+ snapshot = {
1850
+ ...snapshot,
1851
+ error: error instanceof Error ? error.message : String(error),
1852
+ isRunning: false
1853
+ };
1854
+ emit();
1855
+ throw error;
1856
+ }
1857
+ };
1858
+ const close = () => {
1859
+ closed = true;
1860
+ listeners.clear();
1861
+ };
1862
+ return {
1863
+ close,
1864
+ getServerSnapshot: () => snapshot,
1865
+ getSnapshot: () => snapshot,
1866
+ run,
1867
+ subscribe: (listener) => {
1868
+ listeners.add(listener);
1869
+ return () => {
1870
+ listeners.delete(listener);
1871
+ };
1872
+ }
1873
+ };
1874
+ };
1875
+
1876
+ // src/client/providerSimulationControlsWidget.ts
1877
+ var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1878
+ var formatKind = (kind) => (kind ?? "stt").toUpperCase();
1879
+ var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
1880
+ const configuredProviders = options.providers.filter((provider) => provider.configured !== false);
1881
+ const fallbackReady = !options.fallbackRequiredProvider || configuredProviders.some((entry) => entry.provider === options.fallbackRequiredProvider);
1882
+ const failureProviders = (options.failureProviders ? options.failureProviders.map((provider) => ({ provider })) : configuredProviders).filter((provider) => configuredProviders.some((entry) => entry.provider === provider.provider));
1883
+ return {
1884
+ canSimulateFailure: configuredProviders.length > 0 && fallbackReady,
1885
+ description: options.failureMessage ?? `Simulate ${formatKind(options.kind)} provider failure and recovery without changing credentials.`,
1886
+ error: snapshot.error,
1887
+ failureProviders,
1888
+ isRunning: snapshot.isRunning,
1889
+ label: snapshot.isRunning ? `Running ${snapshot.mode ?? "simulation"}` : snapshot.lastResult ? `${snapshot.lastResult.provider} ${snapshot.lastResult.mode} simulated` : configuredProviders.length ? `${configuredProviders.length} configured` : "No configured providers",
1890
+ providers: configuredProviders,
1891
+ resultText: snapshot.lastResult ? JSON.stringify(snapshot.lastResult, null, 2) : null,
1892
+ title: options.title ?? `${formatKind(options.kind)} Failure Simulation`
1893
+ };
1894
+ };
1895
+ var renderVoiceProviderSimulationControlsHTML = (snapshot, options) => {
1896
+ const model = createVoiceProviderSimulationControlsViewModel(snapshot, options);
1897
+ 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("");
1898
+ 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("");
1899
+ return `<section class="absolute-voice-provider-simulation absolute-voice-provider-simulation--${snapshot.error ? "error" : snapshot.isRunning ? "running" : "ready"}">
1900
+ <header class="absolute-voice-provider-simulation__header">
1901
+ <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml6(model.title)}</span>
1902
+ <strong class="absolute-voice-provider-simulation__label">${escapeHtml6(model.label)}</strong>
1903
+ </header>
1904
+ <p class="absolute-voice-provider-simulation__description">${escapeHtml6(model.description)}</p>
1905
+ ${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml6(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
1906
+ <div class="absolute-voice-provider-simulation__actions">${failureButtons}${recoveryButtons}</div>
1907
+ ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml6(snapshot.error)}</p>` : ""}
1908
+ ${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml6(model.resultText)}</pre>` : ""}
1909
+ </section>`;
1910
+ };
1911
+ var bindVoiceProviderSimulationControls = (element, store) => {
1912
+ const onClick = (event) => {
1913
+ const target = event.target;
1914
+ if (!(target instanceof HTMLElement)) {
1915
+ return;
1916
+ }
1917
+ const failProvider = target.getAttribute("data-voice-provider-fail");
1918
+ const recoverProvider = target.getAttribute("data-voice-provider-recover");
1919
+ if (failProvider) {
1920
+ store.run(failProvider, "failure").catch(() => {});
1921
+ }
1922
+ if (recoverProvider) {
1923
+ store.run(recoverProvider, "recovery").catch(() => {});
1924
+ }
1925
+ };
1926
+ element.addEventListener("click", onClick);
1927
+ return () => element.removeEventListener("click", onClick);
1928
+ };
1929
+ var mountVoiceProviderSimulationControls = (element, options) => {
1930
+ const store = createVoiceProviderSimulationControlsStore(options);
1931
+ const render = () => {
1932
+ element.innerHTML = renderVoiceProviderSimulationControlsHTML(store.getSnapshot(), options);
1933
+ };
1934
+ const unsubscribeStore = store.subscribe(render);
1935
+ const unsubscribeDom = bindVoiceProviderSimulationControls(element, store);
1936
+ render();
1937
+ return {
1938
+ close: () => {
1939
+ unsubscribeDom();
1940
+ unsubscribeStore();
1941
+ store.close();
1942
+ },
1943
+ run: store.run
1944
+ };
1945
+ };
1946
+ var defineVoiceProviderSimulationControlsElement = (tagName = "absolute-voice-provider-simulation") => {
1947
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
1948
+ return;
1949
+ }
1950
+ customElements.define(tagName, class AbsoluteVoiceProviderSimulationElement extends HTMLElement {
1951
+ mounted;
1952
+ connectedCallback() {
1953
+ const providers = (this.getAttribute("providers") ?? "").split(",").map((provider) => provider.trim()).filter(Boolean).map((provider) => ({ provider }));
1954
+ const failureProviders = (this.getAttribute("failure-providers") ?? "").split(",").map((provider) => provider.trim()).filter(Boolean);
1955
+ this.mounted = mountVoiceProviderSimulationControls(this, {
1956
+ failureProviders: failureProviders.length ? failureProviders : undefined,
1957
+ fallbackRequiredMessage: this.getAttribute("fallback-required-message") ?? undefined,
1958
+ fallbackRequiredProvider: this.getAttribute("fallback-required-provider") ?? undefined,
1959
+ failureMessage: this.getAttribute("failure-message") ?? undefined,
1960
+ kind: this.getAttribute("kind") ?? "stt",
1961
+ pathPrefix: this.getAttribute("path-prefix") ?? undefined,
1962
+ providers,
1963
+ title: this.getAttribute("title") ?? undefined
1964
+ });
1965
+ }
1966
+ disconnectedCallback() {
1967
+ this.mounted?.close();
1968
+ this.mounted = undefined;
1969
+ }
1970
+ });
1971
+ };
1972
+
1973
+ // src/vue/useVoiceProviderSimulationControls.ts
1974
+ import { onUnmounted as onUnmounted6, ref as ref6 } from "vue";
1975
+ function useVoiceProviderSimulationControls(options) {
1976
+ const store = createVoiceProviderSimulationControlsStore(options);
1977
+ const error = ref6(null);
1978
+ const isRunning = ref6(false);
1979
+ const lastResult = ref6(null);
1980
+ const mode = ref6(null);
1981
+ const provider = ref6(null);
1982
+ const updatedAt = ref6(undefined);
1983
+ const sync = () => {
1984
+ const snapshot = store.getSnapshot();
1985
+ error.value = snapshot.error;
1986
+ isRunning.value = snapshot.isRunning;
1987
+ lastResult.value = snapshot.lastResult;
1988
+ mode.value = snapshot.mode;
1989
+ provider.value = snapshot.provider;
1990
+ updatedAt.value = snapshot.updatedAt;
1991
+ };
1992
+ const unsubscribe = store.subscribe(sync);
1993
+ sync();
1994
+ onUnmounted6(() => {
1995
+ unsubscribe();
1996
+ store.close();
1997
+ });
1998
+ return {
1999
+ error,
2000
+ isRunning,
2001
+ lastResult,
2002
+ mode,
2003
+ provider,
2004
+ run: store.run,
2005
+ updatedAt
2006
+ };
2007
+ }
2008
+
2009
+ // src/vue/VoiceProviderSimulationControls.ts
2010
+ var VoiceProviderSimulationControls = defineComponent6({
2011
+ name: "VoiceProviderSimulationControls",
2012
+ props: {
2013
+ class: { default: "", type: String },
2014
+ fallbackRequiredMessage: { default: undefined, type: String },
2015
+ fallbackRequiredProvider: { default: undefined, type: String },
2016
+ failureMessage: { default: undefined, type: String },
2017
+ failureProviders: {
2018
+ default: undefined,
2019
+ type: Array
2020
+ },
2021
+ kind: { default: "stt", type: String },
2022
+ pathPrefix: { default: undefined, type: String },
2023
+ providers: {
2024
+ required: true,
2025
+ type: Array
2026
+ },
2027
+ title: { default: undefined, type: String }
2028
+ },
2029
+ setup(props) {
2030
+ const options = {
2031
+ fallbackRequiredMessage: props.fallbackRequiredMessage,
2032
+ fallbackRequiredProvider: props.fallbackRequiredProvider,
2033
+ failureMessage: props.failureMessage,
2034
+ failureProviders: props.failureProviders,
2035
+ kind: props.kind,
2036
+ pathPrefix: props.pathPrefix,
2037
+ providers: props.providers,
2038
+ title: props.title
2039
+ };
2040
+ const controls = useVoiceProviderSimulationControls(options);
2041
+ const model = computed(() => createVoiceProviderSimulationControlsViewModel({
2042
+ error: controls.error.value,
2043
+ isRunning: controls.isRunning.value,
2044
+ lastResult: controls.lastResult.value,
2045
+ mode: controls.mode.value,
2046
+ provider: controls.provider.value,
2047
+ updatedAt: controls.updatedAt.value
2048
+ }, options));
2049
+ const run = (provider, mode) => {
2050
+ controls.run(provider, mode).catch(() => {});
2051
+ };
2052
+ return () => h6("section", {
2053
+ class: [
2054
+ "absolute-voice-provider-simulation",
2055
+ `absolute-voice-provider-simulation--${controls.error.value ? "error" : controls.isRunning.value ? "running" : "ready"}`,
2056
+ props.class
2057
+ ]
2058
+ }, [
2059
+ h6("header", { class: "absolute-voice-provider-simulation__header" }, [
2060
+ h6("span", { class: "absolute-voice-provider-simulation__eyebrow" }, model.value.title),
2061
+ h6("strong", { class: "absolute-voice-provider-simulation__label" }, model.value.label)
2062
+ ]),
2063
+ h6("p", { class: "absolute-voice-provider-simulation__description" }, model.value.description),
2064
+ model.value.canSimulateFailure ? null : h6("p", { class: "absolute-voice-provider-simulation__empty" }, props.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure."),
2065
+ h6("div", { class: "absolute-voice-provider-simulation__actions" }, [
2066
+ ...model.value.failureProviders.map((provider) => h6("button", {
2067
+ disabled: !model.value.canSimulateFailure || controls.isRunning.value,
2068
+ key: `fail-${provider.provider}`,
2069
+ onClick: () => run(provider.provider, "failure"),
2070
+ type: "button"
2071
+ }, `Simulate ${provider.provider} ${props.kind.toUpperCase()} failure`)),
2072
+ ...model.value.providers.map((provider) => h6("button", {
2073
+ disabled: controls.isRunning.value,
2074
+ key: `recover-${provider.provider}`,
2075
+ onClick: () => run(provider.provider, "recovery"),
2076
+ type: "button"
2077
+ }, `Mark ${provider.provider} recovered`))
2078
+ ]),
2079
+ controls.error.value ? h6("p", { class: "absolute-voice-provider-simulation__error" }, controls.error.value) : null,
2080
+ model.value.resultText ? h6("pre", { class: "absolute-voice-provider-simulation__result" }, model.value.resultText) : null
2081
+ ]);
2082
+ }
2083
+ });
2084
+ // src/vue/VoiceProviderCapabilities.ts
2085
+ import { computed as computed2, defineComponent as defineComponent7, h as h7 } from "vue";
2086
+
2087
+ // src/client/providerCapabilities.ts
2088
+ var fetchVoiceProviderCapabilities = async (path = "/api/provider-capabilities", options = {}) => {
2089
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2090
+ const response = await fetchImpl(path);
2091
+ if (!response.ok) {
2092
+ throw new Error(`Voice provider capabilities failed: HTTP ${response.status}`);
2093
+ }
2094
+ return await response.json();
2095
+ };
2096
+ var createVoiceProviderCapabilitiesStore = (path = "/api/provider-capabilities", options = {}) => {
2097
+ const listeners = new Set;
2098
+ let closed = false;
2099
+ let timer;
2100
+ let snapshot = {
2101
+ error: null,
2102
+ isLoading: false
2103
+ };
2104
+ const emit = () => {
2105
+ for (const listener of listeners) {
2106
+ listener();
2107
+ }
2108
+ };
2109
+ const refresh = async () => {
2110
+ if (closed) {
2111
+ return snapshot.report;
2112
+ }
2113
+ snapshot = {
2114
+ ...snapshot,
2115
+ error: null,
2116
+ isLoading: true
2117
+ };
2118
+ emit();
2119
+ try {
2120
+ const report = await fetchVoiceProviderCapabilities(path, options);
2121
+ snapshot = {
2122
+ error: null,
2123
+ isLoading: false,
2124
+ report,
2125
+ updatedAt: Date.now()
2126
+ };
2127
+ emit();
2128
+ return report;
2129
+ } catch (error) {
2130
+ snapshot = {
2131
+ ...snapshot,
2132
+ error: error instanceof Error ? error.message : String(error),
2133
+ isLoading: false
2134
+ };
2135
+ emit();
2136
+ throw error;
2137
+ }
2138
+ };
2139
+ const close = () => {
2140
+ closed = true;
2141
+ if (timer) {
2142
+ clearInterval(timer);
2143
+ timer = undefined;
2144
+ }
2145
+ listeners.clear();
2146
+ };
2147
+ if (options.intervalMs && options.intervalMs > 0) {
2148
+ timer = setInterval(() => {
2149
+ refresh().catch(() => {});
2150
+ }, options.intervalMs);
2151
+ }
2152
+ return {
2153
+ close,
2154
+ getServerSnapshot: () => snapshot,
2155
+ getSnapshot: () => snapshot,
2156
+ refresh,
2157
+ subscribe: (listener) => {
2158
+ listeners.add(listener);
2159
+ return () => {
2160
+ listeners.delete(listener);
2161
+ };
2162
+ }
2163
+ };
2164
+ };
2165
+
2166
+ // src/client/providerCapabilitiesWidget.ts
2167
+ var DEFAULT_TITLE6 = "Provider Capabilities";
2168
+ var DEFAULT_DESCRIPTION6 = "Configured, selected, and healthy voice providers for this deployment.";
2169
+ var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2170
+ var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
2171
+ var formatKind2 = (kind) => kind.toUpperCase();
2172
+ var formatStatus2 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
2173
+ var getCapabilityDetail = (capability) => {
2174
+ if (!capability.configured) {
2175
+ return "Not configured in this deployment.";
2176
+ }
2177
+ if (capability.selected) {
2178
+ return `Selected ${capability.kind.toUpperCase()} provider for new sessions.`;
2179
+ }
2180
+ if (capability.health?.status === "healthy") {
2181
+ return "Configured and healthy fallback candidate.";
2182
+ }
2183
+ if (capability.health?.status === "idle") {
2184
+ return "Configured; no traffic observed yet.";
2185
+ }
2186
+ if (capability.health?.lastError) {
2187
+ return capability.health.lastError;
2188
+ }
2189
+ return "Configured and available.";
2190
+ };
2191
+ var isWarningStatus = (status) => status === "degraded" || status === "rate-limited" || status === "suppressed" || status === "unconfigured";
2192
+ var createVoiceProviderCapabilitiesViewModel = (snapshot, options = {}) => {
2193
+ const capabilities = (snapshot.report?.capabilities ?? []).map((capability) => ({
2194
+ ...capability,
2195
+ detail: getCapabilityDetail(capability),
2196
+ label: `${formatProvider(capability.provider)} ${formatKind2(capability.kind)}`,
2197
+ rows: [
2198
+ { label: "Status", value: formatStatus2(capability.status) },
2199
+ { label: "Selected", value: capability.selected ? "Yes" : "No" },
2200
+ { label: "Model", value: capability.model ?? "Default" },
2201
+ {
2202
+ label: "Features",
2203
+ value: capability.features?.join(", ") || "Not specified"
2204
+ },
2205
+ { label: "Runs", value: String(capability.health?.runCount ?? 0) },
2206
+ { label: "Errors", value: String(capability.health?.errorCount ?? 0) }
2207
+ ]
2208
+ }));
2209
+ const warningCount = capabilities.filter((capability) => isWarningStatus(capability.status)).length;
2210
+ const selectedCount = snapshot.report?.selected ?? capabilities.filter((capability) => capability.selected).length;
2211
+ return {
2212
+ capabilities,
2213
+ description: options.description ?? DEFAULT_DESCRIPTION6,
2214
+ error: snapshot.error,
2215
+ isLoading: snapshot.isLoading,
2216
+ label: snapshot.error ? "Unavailable" : capabilities.length ? warningCount > 0 ? `${warningCount} needs attention` : `${selectedCount} selected` : snapshot.isLoading ? "Checking" : "No capabilities",
2217
+ status: snapshot.error ? "error" : capabilities.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
2218
+ title: options.title ?? DEFAULT_TITLE6,
2219
+ updatedAt: snapshot.updatedAt
2220
+ };
2221
+ };
2222
+ var renderVoiceProviderCapabilitiesHTML = (snapshot, options = {}) => {
2223
+ const model = createVoiceProviderCapabilitiesViewModel(snapshot, options);
2224
+ 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)}">
2225
+ <header>
2226
+ <strong>${escapeHtml7(capability.label)}</strong>
2227
+ <span>${escapeHtml7(formatStatus2(capability.status))}</span>
2228
+ </header>
2229
+ <p>${escapeHtml7(capability.detail)}</p>
2230
+ <dl>${capability.rows.map((row) => `<div>
2231
+ <dt>${escapeHtml7(row.label)}</dt>
2232
+ <dd>${escapeHtml7(row.value)}</dd>
2233
+ </div>`).join("")}</dl>
2234
+ </article>`).join("")}</div>` : '<p class="absolute-voice-provider-capabilities__empty">Configure provider capabilities to see deployment coverage.</p>';
2235
+ return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml7(model.status)}">
2236
+ <header class="absolute-voice-provider-capabilities__header">
2237
+ <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml7(model.title)}</span>
2238
+ <strong class="absolute-voice-provider-capabilities__label">${escapeHtml7(model.label)}</strong>
2239
+ </header>
2240
+ <p class="absolute-voice-provider-capabilities__description">${escapeHtml7(model.description)}</p>
2241
+ ${capabilities}
2242
+ ${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml7(model.error)}</p>` : ""}
2243
+ </section>`;
2244
+ };
2245
+ 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}`;
2246
+ var mountVoiceProviderCapabilities = (element, path = "/api/provider-capabilities", options = {}) => {
2247
+ const store = createVoiceProviderCapabilitiesStore(path, options);
2248
+ const render = () => {
2249
+ element.innerHTML = renderVoiceProviderCapabilitiesHTML(store.getSnapshot(), options);
2250
+ };
2251
+ const unsubscribe = store.subscribe(render);
2252
+ render();
2253
+ store.refresh().catch(() => {});
2254
+ return {
2255
+ close: () => {
2256
+ unsubscribe();
2257
+ store.close();
2258
+ },
2259
+ refresh: store.refresh
2260
+ };
2261
+ };
2262
+ var defineVoiceProviderCapabilitiesElement = (tagName = "absolute-voice-provider-capabilities") => {
2263
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
2264
+ return;
2265
+ }
2266
+ customElements.define(tagName, class AbsoluteVoiceProviderCapabilitiesElement extends HTMLElement {
2267
+ mounted;
2268
+ connectedCallback() {
2269
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
2270
+ this.mounted = mountVoiceProviderCapabilities(this, this.getAttribute("path") ?? "/api/provider-capabilities", {
2271
+ description: this.getAttribute("description") ?? undefined,
2272
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
2273
+ title: this.getAttribute("title") ?? undefined
2274
+ });
2275
+ }
2276
+ disconnectedCallback() {
2277
+ this.mounted?.close();
2278
+ this.mounted = undefined;
2279
+ }
2280
+ });
2281
+ };
2282
+
2283
+ // src/vue/useVoiceProviderCapabilities.ts
2284
+ import { onUnmounted as onUnmounted7, shallowRef as shallowRef6 } from "vue";
2285
+ function useVoiceProviderCapabilities(path = "/api/provider-capabilities", options = {}) {
2286
+ const store = createVoiceProviderCapabilitiesStore(path, options);
2287
+ const error = shallowRef6(null);
2288
+ const isLoading = shallowRef6(false);
2289
+ const report = shallowRef6();
2290
+ const updatedAt = shallowRef6(undefined);
2291
+ const sync = () => {
2292
+ const snapshot = store.getSnapshot();
2293
+ error.value = snapshot.error;
2294
+ isLoading.value = snapshot.isLoading;
2295
+ report.value = snapshot.report;
2296
+ updatedAt.value = snapshot.updatedAt;
2297
+ };
2298
+ const unsubscribe = store.subscribe(sync);
2299
+ sync();
2300
+ store.refresh().catch(() => {});
2301
+ onUnmounted7(() => {
2302
+ unsubscribe();
2303
+ store.close();
2304
+ });
2305
+ return {
2306
+ error,
2307
+ isLoading,
2308
+ refresh: store.refresh,
2309
+ report,
2310
+ updatedAt
2311
+ };
2312
+ }
2313
+
2314
+ // src/vue/VoiceProviderCapabilities.ts
2315
+ var VoiceProviderCapabilities = defineComponent7({
2316
+ name: "VoiceProviderCapabilities",
2317
+ props: {
2318
+ class: {
2319
+ default: "",
2320
+ type: String
2321
+ },
2322
+ description: {
2323
+ default: undefined,
2324
+ type: String
2325
+ },
2326
+ intervalMs: {
2327
+ default: 5000,
2328
+ type: Number
2329
+ },
2330
+ path: {
2331
+ default: "/api/provider-capabilities",
2332
+ type: String
2333
+ },
2334
+ title: {
2335
+ default: undefined,
2336
+ type: String
2337
+ }
2338
+ },
2339
+ setup(props) {
2340
+ const options = {
2341
+ description: props.description,
2342
+ intervalMs: props.intervalMs,
2343
+ title: props.title
2344
+ };
2345
+ const capabilities = useVoiceProviderCapabilities(props.path, options);
2346
+ const model = computed2(() => createVoiceProviderCapabilitiesViewModel({
2347
+ error: capabilities.error.value,
2348
+ isLoading: capabilities.isLoading.value,
2349
+ report: capabilities.report.value,
2350
+ updatedAt: capabilities.updatedAt.value
2351
+ }, options));
2352
+ return () => h7("section", {
2353
+ class: [
2354
+ "absolute-voice-provider-capabilities",
2355
+ `absolute-voice-provider-capabilities--${model.value.status}`,
2356
+ props.class
2357
+ ]
2358
+ }, [
2359
+ h7("header", { class: "absolute-voice-provider-capabilities__header" }, [
2360
+ h7("span", { class: "absolute-voice-provider-capabilities__eyebrow" }, model.value.title),
2361
+ h7("strong", { class: "absolute-voice-provider-capabilities__label" }, model.value.label)
2362
+ ]),
2363
+ h7("p", { class: "absolute-voice-provider-capabilities__description" }, model.value.description),
2364
+ model.value.capabilities.length ? h7("div", { class: "absolute-voice-provider-capabilities__providers" }, model.value.capabilities.map((capability) => h7("article", {
2365
+ class: [
2366
+ "absolute-voice-provider-capabilities__provider",
2367
+ `absolute-voice-provider-capabilities__provider--${capability.status}`
2368
+ ],
2369
+ key: `${capability.kind}:${capability.provider}`
2370
+ }, [
2371
+ h7("header", [
2372
+ h7("strong", capability.label),
2373
+ h7("span", capability.status)
2374
+ ]),
2375
+ h7("p", capability.detail),
2376
+ h7("dl", capability.rows.map((row) => h7("div", { key: row.label }, [
2377
+ h7("dt", row.label),
2378
+ h7("dd", row.value)
2379
+ ])))
2380
+ ]))) : h7("p", { class: "absolute-voice-provider-capabilities__empty" }, "Configure provider capabilities to see deployment coverage."),
2381
+ model.value.error ? h7("p", { class: "absolute-voice-provider-capabilities__error" }, model.value.error) : null
2382
+ ]);
2383
+ }
2384
+ });
2385
+ // src/vue/VoiceProviderContracts.ts
2386
+ import { defineComponent as defineComponent8, h as h8 } from "vue";
2387
+
2388
+ // src/vue/useVoiceProviderContracts.ts
2389
+ import { onUnmounted as onUnmounted8, shallowRef as shallowRef7 } from "vue";
2390
+
2391
+ // src/client/providerContracts.ts
2392
+ var fetchVoiceProviderContracts = async (path = "/api/provider-contracts", options = {}) => {
2393
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2394
+ const response = await fetchImpl(path);
2395
+ if (!response.ok) {
2396
+ throw new Error(`Voice provider contracts failed: HTTP ${response.status}`);
2397
+ }
2398
+ return await response.json();
2399
+ };
2400
+ var createVoiceProviderContractsStore = (path = "/api/provider-contracts", options = {}) => {
2401
+ const listeners = new Set;
2402
+ let closed = false;
2403
+ let timer;
2404
+ let snapshot = {
2405
+ error: null,
2406
+ isLoading: false
2407
+ };
2408
+ const emit = () => {
2409
+ for (const listener of listeners) {
2410
+ listener();
2411
+ }
2412
+ };
2413
+ const refresh = async () => {
2414
+ if (closed) {
2415
+ return snapshot.report;
2416
+ }
2417
+ snapshot = { ...snapshot, error: null, isLoading: true };
2418
+ emit();
2419
+ try {
2420
+ const report = await fetchVoiceProviderContracts(path, options);
2421
+ snapshot = {
2422
+ error: null,
2423
+ isLoading: false,
2424
+ report,
2425
+ updatedAt: Date.now()
2426
+ };
2427
+ emit();
2428
+ return report;
2429
+ } catch (error) {
2430
+ snapshot = {
2431
+ ...snapshot,
2432
+ error: error instanceof Error ? error.message : String(error),
2433
+ isLoading: false
2434
+ };
2435
+ emit();
2436
+ throw error;
2437
+ }
2438
+ };
2439
+ const close = () => {
2440
+ closed = true;
2441
+ if (timer) {
2442
+ clearInterval(timer);
2443
+ timer = undefined;
2444
+ }
2445
+ listeners.clear();
2446
+ };
2447
+ if (options.intervalMs && options.intervalMs > 0) {
2448
+ timer = setInterval(() => {
2449
+ refresh().catch(() => {});
2450
+ }, options.intervalMs);
2451
+ }
2452
+ return {
2453
+ close,
2454
+ getServerSnapshot: () => snapshot,
2455
+ getSnapshot: () => snapshot,
2456
+ refresh,
2457
+ subscribe: (listener) => {
2458
+ listeners.add(listener);
2459
+ return () => {
2460
+ listeners.delete(listener);
2461
+ };
2462
+ }
2463
+ };
2464
+ };
2465
+
2466
+ // src/vue/useVoiceProviderContracts.ts
2467
+ function useVoiceProviderContracts(path = "/api/provider-contracts", options = {}) {
2468
+ const store = createVoiceProviderContractsStore(path, options);
2469
+ const error = shallowRef7(null);
2470
+ const isLoading = shallowRef7(false);
2471
+ const report = shallowRef7();
2472
+ const updatedAt = shallowRef7(undefined);
2473
+ const sync = () => {
2474
+ const snapshot = store.getSnapshot();
2475
+ error.value = snapshot.error;
2476
+ isLoading.value = snapshot.isLoading;
2477
+ report.value = snapshot.report;
2478
+ updatedAt.value = snapshot.updatedAt;
2479
+ };
2480
+ const unsubscribe = store.subscribe(sync);
2481
+ sync();
2482
+ store.refresh().catch(() => {});
2483
+ onUnmounted8(() => {
2484
+ unsubscribe();
2485
+ store.close();
2486
+ });
2487
+ return {
2488
+ error,
2489
+ isLoading,
2490
+ refresh: store.refresh,
2491
+ report,
2492
+ updatedAt
2493
+ };
2494
+ }
2495
+
2496
+ // src/client/providerContractsWidget.ts
2497
+ var DEFAULT_TITLE7 = "Provider Contracts";
2498
+ var DEFAULT_DESCRIPTION7 = "Production contract coverage for provider env, latency, fallback, streaming, and capabilities.";
2499
+ var escapeHtml8 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2500
+ var formatProvider2 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
2501
+ var formatStatus3 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
2502
+ var contractDetail = (row) => {
2503
+ const failing = row.checks.filter((check) => check.status !== "pass");
2504
+ if (failing.length === 0) {
2505
+ return "Provider contract is production-ready.";
2506
+ }
2507
+ return failing.map((check) => `${check.label}: ${check.detail ?? check.status}`).join(" ");
2508
+ };
2509
+ var createVoiceProviderContractsViewModel = (snapshot, options = {}) => {
2510
+ const rows = (snapshot.report?.rows ?? []).map((row) => ({
2511
+ ...row,
2512
+ detail: contractDetail(row),
2513
+ label: `${formatProvider2(row.provider)} ${row.kind.toUpperCase()}`,
2514
+ remediations: row.checks.filter((check) => check.status !== "pass" && check.remediation).map((check) => ({
2515
+ detail: check.remediation?.detail ?? "",
2516
+ href: check.remediation?.href,
2517
+ label: check.remediation?.label ?? check.label
2518
+ })),
2519
+ rows: [
2520
+ { label: "Status", value: formatStatus3(row.status) },
2521
+ { label: "Selected", value: row.selected ? "Yes" : "No" },
2522
+ { label: "Configured", value: row.configured ? "Yes" : "No" },
2523
+ {
2524
+ label: "Checks",
2525
+ value: row.checks.map((check) => `${check.label}: ${formatStatus3(check.status)}`).join(", ")
2526
+ }
2527
+ ]
2528
+ }));
2529
+ const warningCount = snapshot.report ? snapshot.report.failed + snapshot.report.warned : rows.filter((row) => row.status !== "pass").length;
2530
+ return {
2531
+ description: options.description ?? DEFAULT_DESCRIPTION7,
2532
+ error: snapshot.error,
2533
+ isLoading: snapshot.isLoading,
2534
+ label: snapshot.error ? "Unavailable" : rows.length ? warningCount > 0 ? `${warningCount} needs attention` : `${rows.length} passing` : snapshot.isLoading ? "Checking" : "No contracts",
2535
+ rows,
2536
+ status: snapshot.error ? "error" : rows.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
2537
+ title: options.title ?? DEFAULT_TITLE7,
2538
+ updatedAt: snapshot.updatedAt
2539
+ };
2540
+ };
2541
+ var renderVoiceProviderContractsHTML = (snapshot, options = {}) => {
2542
+ const model = createVoiceProviderContractsViewModel(snapshot, options);
2543
+ 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)}">
2544
+ <header>
2545
+ <strong>${escapeHtml8(row.label)}</strong>
2546
+ <span>${escapeHtml8(formatStatus3(row.status))}</span>
2547
+ </header>
2548
+ <p>${escapeHtml8(row.detail)}</p>
2549
+ ${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>` : ""}
2550
+ <dl>${row.rows.map((item) => `<div>
2551
+ <dt>${escapeHtml8(item.label)}</dt>
2552
+ <dd>${escapeHtml8(item.value)}</dd>
2553
+ </div>`).join("")}</dl>
2554
+ </article>`).join("")}</div>` : '<p class="absolute-voice-provider-contracts__empty">Configure provider contracts to see production coverage.</p>';
2555
+ return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${escapeHtml8(model.status)}">
2556
+ <header class="absolute-voice-provider-contracts__header">
2557
+ <span class="absolute-voice-provider-contracts__eyebrow">${escapeHtml8(model.title)}</span>
2558
+ <strong class="absolute-voice-provider-contracts__label">${escapeHtml8(model.label)}</strong>
2559
+ </header>
2560
+ <p class="absolute-voice-provider-contracts__description">${escapeHtml8(model.description)}</p>
2561
+ ${rows}
2562
+ ${model.error ? `<p class="absolute-voice-provider-contracts__error">${escapeHtml8(model.error)}</p>` : ""}
2563
+ </section>`;
2564
+ };
2565
+ 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}`;
2566
+ var mountVoiceProviderContracts = (element, path = "/api/provider-contracts", options = {}) => {
2567
+ const store = createVoiceProviderContractsStore(path, options);
2568
+ const render = () => {
2569
+ element.innerHTML = renderVoiceProviderContractsHTML(store.getSnapshot(), options);
2570
+ };
2571
+ const unsubscribe = store.subscribe(render);
2572
+ render();
2573
+ store.refresh().catch(() => {});
2574
+ return {
2575
+ close: () => {
2576
+ unsubscribe();
2577
+ store.close();
2578
+ },
2579
+ refresh: store.refresh
2580
+ };
2581
+ };
2582
+ var defineVoiceProviderContractsElement = (tagName = "absolute-voice-provider-contracts") => {
2583
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
2584
+ return;
2585
+ }
2586
+ customElements.define(tagName, class AbsoluteVoiceProviderContractsElement extends HTMLElement {
2587
+ mounted;
2588
+ connectedCallback() {
2589
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
2590
+ this.mounted = mountVoiceProviderContracts(this, this.getAttribute("path") ?? "/api/provider-contracts", {
2591
+ description: this.getAttribute("description") ?? undefined,
2592
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
2593
+ title: this.getAttribute("title") ?? undefined
2594
+ });
2595
+ }
2596
+ disconnectedCallback() {
2597
+ this.mounted?.close();
2598
+ this.mounted = undefined;
2599
+ }
2600
+ });
2601
+ };
2602
+
2603
+ // src/vue/VoiceProviderContracts.ts
2604
+ var VoiceProviderContracts = defineComponent8({
2605
+ name: "VoiceProviderContracts",
2606
+ props: {
2607
+ description: String,
2608
+ intervalMs: Number,
2609
+ path: {
2610
+ default: "/api/provider-contracts",
2611
+ type: String
2612
+ },
2613
+ title: String
2614
+ },
2615
+ setup(props) {
2616
+ const state = useVoiceProviderContracts(props.path, {
2617
+ description: props.description,
2618
+ intervalMs: props.intervalMs,
2619
+ title: props.title
2620
+ });
2621
+ return () => {
2622
+ const model = createVoiceProviderContractsViewModel({
2623
+ error: state.error.value,
2624
+ isLoading: state.isLoading.value,
2625
+ report: state.report.value,
2626
+ updatedAt: state.updatedAt.value
2627
+ }, {
2628
+ description: props.description,
2629
+ intervalMs: props.intervalMs,
2630
+ title: props.title
2631
+ });
2632
+ return h8("section", {
2633
+ class: [
2634
+ "absolute-voice-provider-contracts",
2635
+ `absolute-voice-provider-contracts--${model.status}`
2636
+ ]
2637
+ }, [
2638
+ h8("header", { class: "absolute-voice-provider-contracts__header" }, [
2639
+ h8("span", { class: "absolute-voice-provider-contracts__eyebrow" }, model.title),
2640
+ h8("strong", { class: "absolute-voice-provider-contracts__label" }, model.label)
2641
+ ]),
2642
+ h8("p", { class: "absolute-voice-provider-contracts__description" }, model.description),
2643
+ model.rows.length ? h8("div", { class: "absolute-voice-provider-contracts__rows" }, model.rows.map((row) => h8("article", {
2644
+ class: [
2645
+ "absolute-voice-provider-contracts__row",
2646
+ `absolute-voice-provider-contracts__row--${row.status}`
2647
+ ],
2648
+ key: `${row.kind}:${row.provider}`
2649
+ }, [
2650
+ h8("header", [
2651
+ h8("strong", row.label),
2652
+ h8("span", row.status)
2653
+ ]),
2654
+ h8("p", row.detail),
2655
+ row.remediations.length ? h8("ul", {
2656
+ class: "absolute-voice-provider-contracts__remediations"
2657
+ }, row.remediations.map((remediation) => h8("li", {
2658
+ key: `${row.kind}:${row.provider}:${remediation.label}`
2659
+ }, [
2660
+ remediation.href ? h8("a", { href: remediation.href }, remediation.label) : h8("strong", remediation.label),
2661
+ h8("span", remediation.detail)
2662
+ ]))) : null,
2663
+ h8("dl", row.rows.map((item) => h8("div", { key: item.label }, [
2664
+ h8("dt", item.label),
2665
+ h8("dd", item.value)
2666
+ ])))
2667
+ ]))) : h8("p", { class: "absolute-voice-provider-contracts__empty" }, "Configure provider contracts to see production coverage."),
2668
+ model.error ? h8("p", { class: "absolute-voice-provider-contracts__error" }, model.error) : null
2669
+ ]);
2670
+ };
2671
+ }
2672
+ });
2673
+ // src/vue/VoiceProviderStatus.ts
2674
+ import { computed as computed3, defineComponent as defineComponent9, h as h9 } from "vue";
2675
+
2676
+ // src/client/providerStatus.ts
2677
+ var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
2678
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2679
+ const response = await fetchImpl(path);
2680
+ if (!response.ok) {
2681
+ throw new Error(`Voice provider status failed: HTTP ${response.status}`);
2682
+ }
2683
+ return await response.json();
2684
+ };
2685
+ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
2686
+ const listeners = new Set;
2687
+ let closed = false;
2688
+ let timer;
2689
+ let snapshot = {
2690
+ error: null,
2691
+ isLoading: false,
2692
+ providers: []
2693
+ };
2694
+ const emit = () => {
2695
+ for (const listener of listeners) {
2696
+ listener();
2697
+ }
2698
+ };
2699
+ const refresh = async () => {
2700
+ if (closed) {
2701
+ return snapshot.providers;
2702
+ }
2703
+ snapshot = {
2704
+ ...snapshot,
2705
+ error: null,
2706
+ isLoading: true
2707
+ };
2708
+ emit();
2709
+ try {
2710
+ const providers = await fetchVoiceProviderStatus(path, options);
2711
+ snapshot = {
2712
+ error: null,
2713
+ isLoading: false,
2714
+ providers,
2715
+ updatedAt: Date.now()
2716
+ };
2717
+ emit();
2718
+ return providers;
2719
+ } catch (error) {
2720
+ snapshot = {
2721
+ ...snapshot,
2722
+ error: error instanceof Error ? error.message : String(error),
2723
+ isLoading: false
2724
+ };
2725
+ emit();
2726
+ throw error;
2727
+ }
2728
+ };
2729
+ const close = () => {
2730
+ closed = true;
2731
+ if (timer) {
2732
+ clearInterval(timer);
2733
+ timer = undefined;
2734
+ }
2735
+ listeners.clear();
2736
+ };
2737
+ if (options.intervalMs && options.intervalMs > 0) {
2738
+ timer = setInterval(() => {
2739
+ refresh().catch(() => {});
2740
+ }, options.intervalMs);
2741
+ }
2742
+ return {
2743
+ close,
2744
+ getServerSnapshot: () => snapshot,
2745
+ getSnapshot: () => snapshot,
2746
+ refresh,
2747
+ subscribe: (listener) => {
2748
+ listeners.add(listener);
2749
+ return () => {
2750
+ listeners.delete(listener);
2751
+ };
2752
+ }
2753
+ };
2754
+ };
2755
+
2756
+ // src/client/providerStatusWidget.ts
2757
+ var DEFAULT_TITLE8 = "Voice Providers";
2758
+ var DEFAULT_DESCRIPTION8 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
2759
+ var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2760
+ var formatProvider3 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
2761
+ var formatStatus4 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
2762
+ var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
2763
+ var formatSuppression = (value) => typeof value === "number" ? `${Math.ceil(value / 1000)}s` : "None";
2764
+ var getProviderDetail = (provider) => {
2765
+ if (provider.status === "suppressed") {
2766
+ return provider.lastError ? `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)} after ${provider.lastError}.` : `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)}.`;
2767
+ }
2768
+ if (provider.status === "recoverable") {
2769
+ return "Cooldown expired; ready for recovery traffic.";
2770
+ }
2771
+ if (provider.status === "rate-limited") {
2772
+ return "Rate limit detected; router should avoid this provider.";
2773
+ }
2774
+ if (provider.status === "degraded") {
2775
+ return provider.lastError ?? "Recent provider errors detected.";
2776
+ }
2777
+ if (provider.status === "healthy") {
2778
+ return provider.recommended ? "Healthy and currently recommended." : "Healthy and available for routing.";
2779
+ }
2780
+ return "No provider traffic observed yet.";
2781
+ };
2782
+ var isWarningStatus2 = (status) => status === "degraded" || status === "rate-limited" || status === "recoverable" || status === "suppressed";
2783
+ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
2784
+ const providers = snapshot.providers.map((provider) => ({
2785
+ ...provider,
2786
+ detail: getProviderDetail(provider),
2787
+ label: `${formatProvider3(provider.provider)}${provider.recommended ? " recommended" : ""}`,
2788
+ rows: [
2789
+ { label: "Runs", value: String(provider.runCount) },
2790
+ { label: "Avg latency", value: formatLatency(provider.averageElapsedMs) },
2791
+ { label: "Errors", value: String(provider.errorCount) },
2792
+ { label: "Timeouts", value: String(provider.timeoutCount) },
2793
+ { label: "Fallbacks", value: String(provider.fallbackCount) },
2794
+ {
2795
+ label: "Suppression",
2796
+ value: formatSuppression(provider.suppressionRemainingMs)
2797
+ }
2798
+ ]
2799
+ }));
2800
+ const warningCount = providers.filter((provider) => isWarningStatus2(provider.status)).length;
2801
+ const healthyCount = providers.filter((provider) => provider.status === "healthy").length;
2802
+ return {
2803
+ description: options.description ?? DEFAULT_DESCRIPTION8,
2804
+ error: snapshot.error,
2805
+ isLoading: snapshot.isLoading,
2806
+ label: snapshot.error ? "Unavailable" : providers.length ? warningCount > 0 ? `${warningCount} needs attention` : `${healthyCount} healthy` : snapshot.isLoading ? "Checking" : "No provider traffic",
2807
+ providers,
2808
+ status: snapshot.error ? "error" : providers.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
2809
+ title: options.title ?? DEFAULT_TITLE8,
2810
+ updatedAt: snapshot.updatedAt
2811
+ };
2812
+ };
2813
+ var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
2814
+ const model = createVoiceProviderStatusViewModel(snapshot, options);
2815
+ 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)}">
2816
+ <header>
2817
+ <strong>${escapeHtml9(provider.label)}</strong>
2818
+ <span>${escapeHtml9(formatStatus4(provider.status))}</span>
2819
+ </header>
2820
+ <p>${escapeHtml9(provider.detail)}</p>
2821
+ <dl>${provider.rows.map((row) => `<div>
2822
+ <dt>${escapeHtml9(row.label)}</dt>
2823
+ <dd>${escapeHtml9(row.value)}</dd>
2824
+ </div>`).join("")}</dl>
2825
+ </article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
2826
+ return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml9(model.status)}">
2827
+ <header class="absolute-voice-provider-status__header">
2828
+ <span class="absolute-voice-provider-status__eyebrow">${escapeHtml9(model.title)}</span>
2829
+ <strong class="absolute-voice-provider-status__label">${escapeHtml9(model.label)}</strong>
2830
+ </header>
2831
+ <p class="absolute-voice-provider-status__description">${escapeHtml9(model.description)}</p>
2832
+ ${providers}
2833
+ ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml9(model.error)}</p>` : ""}
2834
+ </section>`;
2835
+ };
2836
+ 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}`;
2837
+ var mountVoiceProviderStatus = (element, path = "/api/provider-status", options = {}) => {
2838
+ const store = createVoiceProviderStatusStore(path, options);
2839
+ const render = () => {
2840
+ element.innerHTML = renderVoiceProviderStatusHTML(store.getSnapshot(), options);
2841
+ };
2842
+ const unsubscribe = store.subscribe(render);
2843
+ render();
2844
+ store.refresh().catch(() => {});
2845
+ return {
2846
+ close: () => {
2847
+ unsubscribe();
2848
+ store.close();
2849
+ },
2850
+ refresh: store.refresh
2851
+ };
2852
+ };
2853
+ var defineVoiceProviderStatusElement = (tagName = "absolute-voice-provider-status") => {
2854
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
2855
+ return;
2856
+ }
2857
+ customElements.define(tagName, class AbsoluteVoiceProviderStatusElement extends HTMLElement {
2858
+ mounted;
2859
+ connectedCallback() {
2860
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
2861
+ this.mounted = mountVoiceProviderStatus(this, this.getAttribute("path") ?? "/api/provider-status", {
2862
+ description: this.getAttribute("description") ?? undefined,
2863
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
2864
+ title: this.getAttribute("title") ?? undefined
2865
+ });
2866
+ }
2867
+ disconnectedCallback() {
2868
+ this.mounted?.close();
2869
+ this.mounted = undefined;
2870
+ }
2871
+ });
2872
+ };
2873
+
2874
+ // src/vue/useVoiceProviderStatus.ts
2875
+ import { onUnmounted as onUnmounted9, ref as ref7, shallowRef as shallowRef8 } from "vue";
2876
+ function useVoiceProviderStatus(path = "/api/provider-status", options = {}) {
2877
+ const store = createVoiceProviderStatusStore(path, options);
2878
+ const error = ref7(null);
2879
+ const isLoading = ref7(false);
2880
+ const providers = shallowRef8([]);
2881
+ const updatedAt = ref7(undefined);
2882
+ const sync = () => {
2883
+ const snapshot = store.getSnapshot();
2884
+ error.value = snapshot.error;
2885
+ isLoading.value = snapshot.isLoading;
2886
+ providers.value = [...snapshot.providers];
2887
+ updatedAt.value = snapshot.updatedAt;
2888
+ };
2889
+ const unsubscribe = store.subscribe(sync);
2890
+ sync();
2891
+ store.refresh().catch(() => {});
2892
+ onUnmounted9(() => {
2893
+ unsubscribe();
2894
+ store.close();
2895
+ });
2896
+ return {
2897
+ error,
2898
+ isLoading,
2899
+ providers,
2900
+ refresh: store.refresh,
2901
+ updatedAt
2902
+ };
2903
+ }
2904
+
2905
+ // src/vue/VoiceProviderStatus.ts
2906
+ var VoiceProviderStatus = defineComponent9({
2907
+ name: "VoiceProviderStatus",
2908
+ props: {
2909
+ class: {
2910
+ default: "",
2911
+ type: String
2912
+ },
2913
+ description: {
2914
+ default: undefined,
2915
+ type: String
2916
+ },
2917
+ intervalMs: {
2918
+ default: 5000,
2919
+ type: Number
2920
+ },
2921
+ path: {
2922
+ default: "/api/provider-status",
2923
+ type: String
2924
+ },
2925
+ title: {
2926
+ default: undefined,
2927
+ type: String
2928
+ }
2929
+ },
2930
+ setup(props) {
2931
+ const options = {
2932
+ description: props.description,
2933
+ intervalMs: props.intervalMs,
2934
+ title: props.title
2935
+ };
2936
+ const status = useVoiceProviderStatus(props.path, options);
2937
+ const model = computed3(() => createVoiceProviderStatusViewModel({
2938
+ error: status.error.value,
2939
+ isLoading: status.isLoading.value,
2940
+ providers: status.providers.value,
2941
+ updatedAt: status.updatedAt.value
2942
+ }, options));
2943
+ return () => h9("section", {
2944
+ class: [
2945
+ "absolute-voice-provider-status",
2946
+ `absolute-voice-provider-status--${model.value.status}`,
2947
+ props.class
2948
+ ]
2949
+ }, [
2950
+ h9("header", { class: "absolute-voice-provider-status__header" }, [
2951
+ h9("span", { class: "absolute-voice-provider-status__eyebrow" }, model.value.title),
2952
+ h9("strong", { class: "absolute-voice-provider-status__label" }, model.value.label)
2953
+ ]),
2954
+ h9("p", { class: "absolute-voice-provider-status__description" }, model.value.description),
2955
+ model.value.providers.length ? h9("div", { class: "absolute-voice-provider-status__providers" }, model.value.providers.map((provider) => h9("article", {
2956
+ class: [
2957
+ "absolute-voice-provider-status__provider",
2958
+ `absolute-voice-provider-status__provider--${provider.status}`
2959
+ ],
2960
+ key: provider.provider
2961
+ }, [
2962
+ h9("header", [
2963
+ h9("strong", provider.label),
2964
+ h9("span", provider.status)
2965
+ ]),
2966
+ h9("p", provider.detail),
2967
+ h9("dl", provider.rows.map((row) => h9("div", { key: row.label }, [
2968
+ h9("dt", row.label),
2969
+ h9("dd", row.value)
2970
+ ])))
2971
+ ]))) : h9("p", { class: "absolute-voice-provider-status__empty" }, "Run voice traffic to see provider health."),
2972
+ model.value.error ? h9("p", { class: "absolute-voice-provider-status__error" }, model.value.error) : null
2973
+ ]);
2974
+ }
2975
+ });
2976
+ // src/vue/VoiceRoutingStatus.ts
2977
+ import { computed as computed4, defineComponent as defineComponent10, h as h10 } from "vue";
2978
+
2979
+ // src/client/routingStatus.ts
2980
+ var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
2981
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2982
+ const response = await fetchImpl(path);
2983
+ if (!response.ok) {
2984
+ throw new Error(`Voice routing status failed: HTTP ${response.status}`);
2985
+ }
2986
+ return await response.json();
2987
+ };
2988
+ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {}) => {
2989
+ const listeners = new Set;
2990
+ let closed = false;
2991
+ let timer;
2992
+ let snapshot = {
2993
+ decision: null,
2994
+ error: null,
2995
+ isLoading: false
2996
+ };
2997
+ const emit = () => {
2998
+ for (const listener of listeners) {
2999
+ listener();
3000
+ }
3001
+ };
3002
+ const refresh = async () => {
3003
+ if (closed) {
3004
+ return snapshot.decision;
3005
+ }
3006
+ snapshot = {
3007
+ ...snapshot,
3008
+ error: null,
3009
+ isLoading: true
3010
+ };
3011
+ emit();
3012
+ try {
3013
+ const decision = await fetchVoiceRoutingStatus(path, options);
3014
+ snapshot = {
3015
+ decision,
3016
+ error: null,
3017
+ isLoading: false,
3018
+ updatedAt: Date.now()
3019
+ };
3020
+ emit();
3021
+ return decision;
3022
+ } catch (error) {
3023
+ snapshot = {
3024
+ ...snapshot,
3025
+ error: error instanceof Error ? error.message : String(error),
3026
+ isLoading: false
3027
+ };
3028
+ emit();
3029
+ throw error;
3030
+ }
3031
+ };
3032
+ const close = () => {
3033
+ closed = true;
3034
+ if (timer) {
3035
+ clearInterval(timer);
3036
+ timer = undefined;
3037
+ }
3038
+ listeners.clear();
3039
+ };
3040
+ if (options.intervalMs && options.intervalMs > 0) {
3041
+ timer = setInterval(() => {
3042
+ refresh().catch(() => {});
3043
+ }, options.intervalMs);
3044
+ }
3045
+ return {
3046
+ close,
3047
+ getServerSnapshot: () => snapshot,
3048
+ getSnapshot: () => snapshot,
3049
+ refresh,
3050
+ subscribe: (listener) => {
3051
+ listeners.add(listener);
3052
+ return () => {
3053
+ listeners.delete(listener);
3054
+ };
3055
+ }
3056
+ };
3057
+ };
3058
+
3059
+ // src/client/routingStatusWidget.ts
3060
+ var DEFAULT_TITLE9 = "Voice Routing";
3061
+ var DEFAULT_DESCRIPTION9 = "Latest provider routing decision from the self-hosted trace store.";
3062
+ var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3063
+ var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
3064
+ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
3065
+ const decision = snapshot.decision;
3066
+ const rows = decision ? [
3067
+ { label: "Kind", value: decision.kind.toUpperCase() },
3068
+ { label: "Policy", value: formatValue(decision.routing, "Unknown") },
3069
+ { label: "Provider", value: formatValue(decision.provider, "Unknown") },
3070
+ {
3071
+ label: "Selected",
3072
+ value: formatValue(decision.selectedProvider, "Unknown")
3073
+ },
3074
+ {
3075
+ label: "Fallback",
3076
+ value: formatValue(decision.fallbackProvider)
3077
+ },
3078
+ { label: "Status", value: formatValue(decision.status, "unknown") },
3079
+ {
3080
+ label: "Latency budget",
3081
+ value: typeof decision.latencyBudgetMs === "number" ? `${decision.latencyBudgetMs}ms` : "None"
3082
+ }
3083
+ ] : [];
3084
+ return {
3085
+ decision,
3086
+ description: options.description ?? DEFAULT_DESCRIPTION9,
3087
+ error: snapshot.error,
3088
+ isLoading: snapshot.isLoading,
3089
+ label: snapshot.error ? "Unavailable" : decision ? `${formatValue(decision.kind).toUpperCase()} ${formatValue(decision.status, "unknown")}` : snapshot.isLoading ? "Checking" : "No routing yet",
3090
+ rows,
3091
+ status: snapshot.error ? "error" : decision ? "ready" : snapshot.isLoading ? "loading" : "empty",
3092
+ title: options.title ?? DEFAULT_TITLE9,
3093
+ updatedAt: snapshot.updatedAt
3094
+ };
3095
+ };
3096
+ var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
3097
+ const model = createVoiceRoutingStatusViewModel(snapshot, options);
3098
+ const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
3099
+ <span>${escapeHtml10(row.label)}</span>
3100
+ <strong>${escapeHtml10(row.value)}</strong>
3101
+ </div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
3102
+ return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml10(model.status)}">
3103
+ <header class="absolute-voice-routing-status__header">
3104
+ <span class="absolute-voice-routing-status__eyebrow">${escapeHtml10(model.title)}</span>
3105
+ <strong class="absolute-voice-routing-status__label">${escapeHtml10(model.label)}</strong>
3106
+ </header>
3107
+ <p class="absolute-voice-routing-status__description">${escapeHtml10(model.description)}</p>
3108
+ ${rows}
3109
+ ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml10(model.error)}</p>` : ""}
3110
+ </section>`;
3111
+ };
3112
+ 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}`;
3113
+ var mountVoiceRoutingStatus = (element, path = "/api/routing/latest", options = {}) => {
3114
+ const store = createVoiceRoutingStatusStore(path, options);
3115
+ const render = () => {
3116
+ element.innerHTML = renderVoiceRoutingStatusHTML(store.getSnapshot(), options);
3117
+ };
3118
+ const unsubscribe = store.subscribe(render);
3119
+ render();
3120
+ store.refresh().catch(() => {});
3121
+ return {
3122
+ close: () => {
3123
+ unsubscribe();
3124
+ store.close();
3125
+ },
3126
+ refresh: store.refresh
3127
+ };
3128
+ };
3129
+ var defineVoiceRoutingStatusElement = (tagName = "absolute-voice-routing-status") => {
3130
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
3131
+ return;
3132
+ }
3133
+ customElements.define(tagName, class AbsoluteVoiceRoutingStatusElement extends HTMLElement {
3134
+ mounted;
3135
+ connectedCallback() {
3136
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
3137
+ this.mounted = mountVoiceRoutingStatus(this, this.getAttribute("path") ?? "/api/routing/latest", {
3138
+ description: this.getAttribute("description") ?? undefined,
3139
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
3140
+ title: this.getAttribute("title") ?? undefined
3141
+ });
3142
+ }
3143
+ disconnectedCallback() {
3144
+ this.mounted?.close();
3145
+ this.mounted = undefined;
3146
+ }
3147
+ });
3148
+ };
3149
+
3150
+ // src/vue/useVoiceRoutingStatus.ts
3151
+ import { onUnmounted as onUnmounted10, ref as ref8, shallowRef as shallowRef9 } from "vue";
3152
+ function useVoiceRoutingStatus(path = "/api/routing/latest", options = {}) {
3153
+ const store = createVoiceRoutingStatusStore(path, options);
3154
+ const decision = shallowRef9(null);
3155
+ const error = ref8(null);
3156
+ const isLoading = ref8(false);
3157
+ const updatedAt = ref8(undefined);
3158
+ const sync = () => {
3159
+ const snapshot = store.getSnapshot();
3160
+ decision.value = snapshot.decision;
3161
+ error.value = snapshot.error;
3162
+ isLoading.value = snapshot.isLoading;
3163
+ updatedAt.value = snapshot.updatedAt;
3164
+ };
3165
+ const unsubscribe = store.subscribe(sync);
3166
+ sync();
3167
+ store.refresh().catch(() => {});
3168
+ onUnmounted10(() => {
3169
+ unsubscribe();
3170
+ store.close();
3171
+ });
3172
+ return {
3173
+ decision,
3174
+ error,
3175
+ isLoading,
3176
+ refresh: store.refresh,
3177
+ updatedAt
3178
+ };
3179
+ }
3180
+
3181
+ // src/vue/VoiceRoutingStatus.ts
3182
+ var VoiceRoutingStatus = defineComponent10({
3183
+ name: "VoiceRoutingStatus",
3184
+ props: {
3185
+ class: {
3186
+ default: "",
3187
+ type: String
3188
+ },
3189
+ description: {
3190
+ default: undefined,
3191
+ type: String
3192
+ },
3193
+ intervalMs: {
3194
+ default: 5000,
3195
+ type: Number
3196
+ },
3197
+ path: {
3198
+ default: "/api/routing/latest",
3199
+ type: String
3200
+ },
3201
+ title: {
3202
+ default: undefined,
3203
+ type: String
3204
+ }
3205
+ },
3206
+ setup(props) {
3207
+ const options = {
3208
+ description: props.description,
3209
+ intervalMs: props.intervalMs,
3210
+ title: props.title
3211
+ };
3212
+ const status = useVoiceRoutingStatus(props.path, options);
3213
+ const model = computed4(() => createVoiceRoutingStatusViewModel({
3214
+ decision: status.decision.value,
3215
+ error: status.error.value,
3216
+ isLoading: status.isLoading.value,
3217
+ updatedAt: status.updatedAt.value
3218
+ }, options));
3219
+ return () => h10("section", {
3220
+ class: [
3221
+ "absolute-voice-routing-status",
3222
+ `absolute-voice-routing-status--${model.value.status}`,
3223
+ props.class
3224
+ ]
3225
+ }, [
3226
+ h10("header", { class: "absolute-voice-routing-status__header" }, [
3227
+ h10("span", { class: "absolute-voice-routing-status__eyebrow" }, model.value.title),
3228
+ h10("strong", { class: "absolute-voice-routing-status__label" }, model.value.label)
3229
+ ]),
3230
+ h10("p", { class: "absolute-voice-routing-status__description" }, model.value.description),
3231
+ model.value.rows.length ? h10("div", { class: "absolute-voice-routing-status__grid" }, model.value.rows.map((row) => h10("div", { key: row.label }, [
3232
+ h10("span", row.label),
3233
+ h10("strong", row.value)
3234
+ ]))) : h10("p", { class: "absolute-voice-routing-status__empty" }, "Start a voice session to see the selected provider."),
3235
+ model.value.error ? h10("p", { class: "absolute-voice-routing-status__error" }, model.value.error) : null
3236
+ ]);
3237
+ }
3238
+ });
3239
+ // src/vue/useVoiceAgentSquadStatus.ts
3240
+ import { onUnmounted as onUnmounted11, ref as ref9, shallowRef as shallowRef10 } from "vue";
3241
+
3242
+ // src/client/traceTimeline.ts
3243
+ var fetchVoiceTraceTimeline = async (path = "/api/voice-traces", options = {}) => {
3244
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3245
+ const response = await fetchImpl(path);
3246
+ if (!response.ok) {
3247
+ throw new Error(`Voice trace timeline failed: HTTP ${response.status}`);
3248
+ }
3249
+ return await response.json();
3250
+ };
3251
+ var createVoiceTraceTimelineStore = (path = "/api/voice-traces", options = {}) => {
3252
+ const listeners = new Set;
3253
+ let closed = false;
3254
+ let timer;
3255
+ let snapshot = {
3256
+ error: null,
3257
+ isLoading: false,
3258
+ report: null
3259
+ };
3260
+ const emit = () => {
3261
+ for (const listener of listeners) {
3262
+ listener();
3263
+ }
3264
+ };
3265
+ const refresh = async () => {
3266
+ if (closed) {
3267
+ return snapshot.report;
3268
+ }
3269
+ snapshot = {
3270
+ ...snapshot,
3271
+ error: null,
3272
+ isLoading: true
3273
+ };
3274
+ emit();
3275
+ try {
3276
+ const report = await fetchVoiceTraceTimeline(path, options);
3277
+ snapshot = {
3278
+ error: null,
3279
+ isLoading: false,
3280
+ report,
3281
+ updatedAt: Date.now()
3282
+ };
3283
+ emit();
3284
+ return report;
3285
+ } catch (error) {
3286
+ snapshot = {
3287
+ ...snapshot,
3288
+ error: error instanceof Error ? error.message : String(error),
3289
+ isLoading: false
3290
+ };
3291
+ emit();
3292
+ throw error;
3293
+ }
3294
+ };
3295
+ const close = () => {
3296
+ closed = true;
3297
+ if (timer) {
3298
+ clearInterval(timer);
3299
+ timer = undefined;
3300
+ }
3301
+ listeners.clear();
3302
+ };
3303
+ if (options.intervalMs && options.intervalMs > 0) {
3304
+ timer = setInterval(() => {
3305
+ refresh().catch(() => {});
3306
+ }, options.intervalMs);
3307
+ }
3308
+ return {
3309
+ close,
3310
+ getServerSnapshot: () => snapshot,
3311
+ getSnapshot: () => snapshot,
3312
+ refresh,
3313
+ subscribe: (listener) => {
3314
+ listeners.add(listener);
3315
+ return () => {
3316
+ listeners.delete(listener);
3317
+ };
3318
+ }
3319
+ };
3320
+ };
3321
+
3322
+ // src/client/agentSquadStatus.ts
3323
+ var getString = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
3324
+ var getPayloadString = (event, key) => getString(event.payload?.[key]);
3325
+ var eventStatus = (event) => {
3326
+ const status = getPayloadString(event, "status");
3327
+ if (status === "blocked")
3328
+ return "blocked";
3329
+ if (status === "unknown-target")
3330
+ return "unknown-target";
3331
+ if (status === "allowed")
3332
+ return "handoff";
3333
+ return event.type === "agent.result" ? "active" : "handoff";
3334
+ };
3335
+ var deriveSessionSpecialist = (session) => {
3336
+ const events = [...session.events].sort((left, right) => left.at - right.at);
3337
+ const agentEvents = events.filter((event) => event.type === "agent.handoff" || event.type === "agent.context" || event.type === "agent.result" || event.type === "agent.model");
3338
+ const latest = agentEvents.at(-1);
3339
+ if (!latest) {
3340
+ return {
3341
+ lastEventAt: session.lastEventAt,
3342
+ sessionId: session.sessionId,
3343
+ status: "idle"
3344
+ };
3345
+ }
3346
+ const handoffEvents = events.filter((event) => event.type === "agent.handoff");
3347
+ const lastHandoff = handoffEvents.at(-1);
3348
+ const latestAgentId = getPayloadString(latest, "agentId");
3349
+ const handoffStatus = lastHandoff ? eventStatus(lastHandoff) : undefined;
3350
+ const currentTarget = handoffStatus === "blocked" || handoffStatus === "unknown-target" ? getPayloadString(lastHandoff, "fromAgentId") ?? latestAgentId : getPayloadString(lastHandoff ?? latest, "targetAgentId") ?? latestAgentId;
3351
+ return {
3352
+ fromAgentId: getPayloadString(lastHandoff ?? latest, "fromAgentId"),
3353
+ lastEventAt: latest.at,
3354
+ reason: getPayloadString(lastHandoff ?? latest, "reason") ?? getPayloadString(latest, "handoffTarget"),
3355
+ sessionId: session.sessionId,
3356
+ status: lastHandoff ? eventStatus(lastHandoff) : "active",
3357
+ summary: getPayloadString(lastHandoff ?? latest, "summary"),
3358
+ targetAgentId: currentTarget,
3359
+ turnId: latest.turnId
3360
+ };
3361
+ };
3362
+ var buildVoiceAgentSquadStatusReport = (timeline, options = {}) => {
3363
+ const sessions = (timeline?.sessions ?? []).filter((session) => !options.sessionId || session.sessionId === options.sessionId).map(deriveSessionSpecialist).sort((left, right) => (right.lastEventAt ?? 0) - (left.lastEventAt ?? 0));
3364
+ const active = sessions.filter((session) => session.status !== "idle");
3365
+ return {
3366
+ active,
3367
+ checkedAt: timeline?.checkedAt,
3368
+ current: active[0] ?? sessions[0],
3369
+ sessionCount: sessions.length,
3370
+ sessions
3371
+ };
3372
+ };
3373
+ var createVoiceAgentSquadStatusStore = (path = "/api/voice-traces", options = {}) => {
3374
+ const timelineStore = createVoiceTraceTimelineStore(path, options);
3375
+ const getReport = () => buildVoiceAgentSquadStatusReport(timelineStore.getSnapshot().report, {
3376
+ sessionId: options.sessionId
3377
+ });
3378
+ const getSnapshot = () => {
3379
+ const snapshot = timelineStore.getSnapshot();
3380
+ return {
3381
+ error: snapshot.error,
3382
+ isLoading: snapshot.isLoading,
3383
+ report: getReport(),
3384
+ updatedAt: snapshot.updatedAt
3385
+ };
3386
+ };
3387
+ return {
3388
+ close: timelineStore.close,
3389
+ getServerSnapshot: getSnapshot,
3390
+ getSnapshot,
3391
+ refresh: timelineStore.refresh,
3392
+ subscribe: timelineStore.subscribe
3393
+ };
3394
+ };
3395
+
3396
+ // src/vue/useVoiceAgentSquadStatus.ts
3397
+ function useVoiceAgentSquadStatus(path = "/api/voice-traces", options = {}) {
3398
+ const store = createVoiceAgentSquadStatusStore(path, options);
3399
+ const current = shallowRef10(undefined);
3400
+ const error = ref9(null);
3401
+ const isLoading = ref9(false);
3402
+ const report = shallowRef10(undefined);
3403
+ const updatedAt = ref9(undefined);
3404
+ const sync = () => {
3405
+ const snapshot = store.getSnapshot();
3406
+ current.value = snapshot.report.current;
3407
+ error.value = snapshot.error;
3408
+ isLoading.value = snapshot.isLoading;
3409
+ report.value = snapshot.report;
3410
+ updatedAt.value = snapshot.updatedAt;
3411
+ };
3412
+ const unsubscribe = store.subscribe(sync);
3413
+ sync();
3414
+ if (typeof window !== "undefined") {
3415
+ store.refresh().catch(() => {});
3416
+ }
3417
+ onUnmounted11(() => {
3418
+ unsubscribe();
3419
+ store.close();
3420
+ });
3421
+ return {
3422
+ current,
3423
+ error,
3424
+ isLoading,
3425
+ refresh: store.refresh,
3426
+ report,
3427
+ updatedAt
3428
+ };
3429
+ }
3430
+ // src/vue/VoiceTurnLatency.ts
3431
+ import { computed as computed5, defineComponent as defineComponent11, h as h11 } from "vue";
3432
+
3433
+ // src/client/turnLatency.ts
3434
+ var fetchVoiceTurnLatency = async (path = "/api/turn-latency", options = {}) => {
3435
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3436
+ const response = await fetchImpl(path);
3437
+ if (!response.ok) {
3438
+ throw new Error(`Voice turn latency failed: HTTP ${response.status}`);
3439
+ }
3440
+ return await response.json();
3441
+ };
3442
+ var runVoiceTurnLatencyProof = async (path, options = {}) => {
3443
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3444
+ const response = await fetchImpl(path, { method: "POST" });
3445
+ if (!response.ok) {
3446
+ throw new Error(`Voice turn latency proof failed: HTTP ${response.status}`);
3447
+ }
3448
+ return response.json();
3449
+ };
3450
+ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) => {
3451
+ const listeners = new Set;
3452
+ let closed = false;
3453
+ let timer;
3454
+ let snapshot = {
3455
+ error: null,
3456
+ isLoading: false
3457
+ };
3458
+ const emit = () => {
3459
+ for (const listener of listeners) {
3460
+ listener();
3461
+ }
3462
+ };
3463
+ const refresh = async () => {
3464
+ if (closed) {
3465
+ return snapshot.report;
3466
+ }
3467
+ snapshot = { ...snapshot, error: null, isLoading: true };
3468
+ emit();
3469
+ try {
3470
+ const report = await fetchVoiceTurnLatency(path, options);
3471
+ snapshot = {
3472
+ error: null,
3473
+ isLoading: false,
3474
+ report,
3475
+ updatedAt: Date.now()
3476
+ };
3477
+ emit();
3478
+ return report;
3479
+ } catch (error) {
3480
+ snapshot = {
3481
+ ...snapshot,
3482
+ error: error instanceof Error ? error.message : String(error),
3483
+ isLoading: false
3484
+ };
3485
+ emit();
3486
+ throw error;
3487
+ }
3488
+ };
3489
+ const runProof = async () => {
3490
+ if (!options.proofPath) {
3491
+ throw new Error("Voice turn latency proof path is not configured.");
3492
+ }
3493
+ snapshot = { ...snapshot, error: null, isLoading: true };
3494
+ emit();
3495
+ try {
3496
+ await runVoiceTurnLatencyProof(options.proofPath, options);
3497
+ return await refresh();
3498
+ } catch (error) {
3499
+ snapshot = {
3500
+ ...snapshot,
3501
+ error: error instanceof Error ? error.message : String(error),
3502
+ isLoading: false
3503
+ };
3504
+ emit();
3505
+ throw error;
3506
+ }
3507
+ };
3508
+ const close = () => {
3509
+ closed = true;
3510
+ if (timer) {
3511
+ clearInterval(timer);
3512
+ timer = undefined;
3513
+ }
3514
+ listeners.clear();
3515
+ };
3516
+ if (options.intervalMs && options.intervalMs > 0) {
3517
+ timer = setInterval(() => {
3518
+ refresh().catch(() => {});
3519
+ }, options.intervalMs);
3520
+ }
3521
+ return {
3522
+ close,
3523
+ getServerSnapshot: () => snapshot,
3524
+ getSnapshot: () => snapshot,
3525
+ refresh,
3526
+ runProof,
3527
+ subscribe: (listener) => {
3528
+ listeners.add(listener);
3529
+ return () => {
3530
+ listeners.delete(listener);
3531
+ };
3532
+ }
3533
+ };
3534
+ };
3535
+
3536
+ // src/client/turnLatencyWidget.ts
3537
+ var DEFAULT_TITLE10 = "Turn Latency";
3538
+ var DEFAULT_DESCRIPTION10 = "Per-turn timing from first transcript to commit and assistant response start.";
3539
+ var DEFAULT_PROOF_LABEL = "Run latency proof";
3540
+ var escapeHtml11 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3541
+ var formatMs2 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
3542
+ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
3543
+ const turns = (snapshot.report?.turns ?? []).map((turn) => ({
3544
+ ...turn,
3545
+ label: turn.text || "Empty turn",
3546
+ rows: turn.stages.map((stage) => ({
3547
+ label: stage.label,
3548
+ value: formatMs2(stage.valueMs)
3549
+ }))
3550
+ }));
3551
+ const warningCount = snapshot.report?.warnings ?? turns.filter((turn) => turn.status === "warn").length;
3552
+ const failedCount = snapshot.report?.failed ?? turns.filter((turn) => turn.status === "fail").length;
3553
+ return {
3554
+ description: options.description ?? DEFAULT_DESCRIPTION10,
3555
+ error: snapshot.error,
3556
+ isLoading: snapshot.isLoading,
3557
+ label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} slow` : warningCount > 0 ? `${warningCount} warnings` : `avg ${formatMs2(snapshot.report?.averageTotalMs)}` : snapshot.isLoading ? "Checking" : "No turns",
3558
+ proofLabel: options.proofPath ? options.proofLabel ?? DEFAULT_PROOF_LABEL : undefined,
3559
+ showProofAction: Boolean(options.proofPath),
3560
+ status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
3561
+ title: options.title ?? DEFAULT_TITLE10,
3562
+ turns,
3563
+ updatedAt: snapshot.updatedAt
3564
+ };
3565
+ };
3566
+ var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
3567
+ const model = createVoiceTurnLatencyViewModel(snapshot, options);
3568
+ 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)}">
3569
+ <header>
3570
+ <strong>${escapeHtml11(turn.label)}</strong>
3571
+ <span>${escapeHtml11(turn.status)}</span>
3572
+ </header>
3573
+ <dl>${turn.rows.map((row) => `<div>
3574
+ <dt>${escapeHtml11(row.label)}</dt>
3575
+ <dd>${escapeHtml11(row.value)}</dd>
3576
+ </div>`).join("")}</dl>
3577
+ </article>`).join("")}</div>` : '<p class="absolute-voice-turn-latency__empty">Complete a voice turn to see latency diagnostics.</p>';
3578
+ return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml11(model.status)}">
3579
+ <header class="absolute-voice-turn-latency__header">
3580
+ <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml11(model.title)}</span>
3581
+ <strong class="absolute-voice-turn-latency__label">${escapeHtml11(model.label)}</strong>
3582
+ </header>
3583
+ <p class="absolute-voice-turn-latency__description">${escapeHtml11(model.description)}</p>
3584
+ ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml11(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
3585
+ ${turns}
3586
+ ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml11(model.error)}</p>` : ""}
3587
+ </section>`;
3588
+ };
3589
+ var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {}) => {
3590
+ const store = createVoiceTurnLatencyStore(path, options);
3591
+ const render = () => {
3592
+ element.innerHTML = renderVoiceTurnLatencyHTML(store.getSnapshot(), options);
3593
+ };
3594
+ const handleClick = (event) => {
3595
+ const target = event.target;
3596
+ if (target instanceof Element && target.closest("[data-absolute-voice-turn-latency-proof]")) {
3597
+ store.runProof().catch(() => {});
3598
+ }
3599
+ };
3600
+ const unsubscribe = store.subscribe(render);
3601
+ element.addEventListener("click", handleClick);
3602
+ render();
3603
+ store.refresh().catch(() => {});
3604
+ return {
3605
+ close: () => {
3606
+ element.removeEventListener("click", handleClick);
3607
+ unsubscribe();
3608
+ store.close();
3609
+ },
3610
+ refresh: store.refresh
3611
+ };
3612
+ };
3613
+ var defineVoiceTurnLatencyElement = (tagName = "absolute-voice-turn-latency") => {
3614
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
3615
+ return;
3616
+ }
3617
+ customElements.define(tagName, class AbsoluteVoiceTurnLatencyElement extends HTMLElement {
3618
+ mounted;
3619
+ connectedCallback() {
3620
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
3621
+ this.mounted = mountVoiceTurnLatency(this, this.getAttribute("path") ?? "/api/turn-latency", {
3622
+ description: this.getAttribute("description") ?? undefined,
3623
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
3624
+ proofLabel: this.getAttribute("proof-label") ?? undefined,
3625
+ proofPath: this.getAttribute("proof-path") ?? undefined,
3626
+ title: this.getAttribute("title") ?? undefined
3627
+ });
3628
+ }
3629
+ disconnectedCallback() {
3630
+ this.mounted?.close();
3631
+ this.mounted = undefined;
3632
+ }
3633
+ });
3634
+ };
3635
+
3636
+ // src/vue/useVoiceTurnLatency.ts
3637
+ import { onUnmounted as onUnmounted12, shallowRef as shallowRef11 } from "vue";
3638
+ function useVoiceTurnLatency(path = "/api/turn-latency", options = {}) {
3639
+ const store = createVoiceTurnLatencyStore(path, options);
3640
+ const error = shallowRef11(null);
3641
+ const isLoading = shallowRef11(false);
3642
+ const report = shallowRef11();
3643
+ const updatedAt = shallowRef11(undefined);
3644
+ const sync = () => {
3645
+ const snapshot = store.getSnapshot();
3646
+ error.value = snapshot.error;
3647
+ isLoading.value = snapshot.isLoading;
3648
+ report.value = snapshot.report;
3649
+ updatedAt.value = snapshot.updatedAt;
3650
+ };
3651
+ const unsubscribe = store.subscribe(sync);
3652
+ sync();
3653
+ store.refresh().catch(() => {});
3654
+ onUnmounted12(() => {
3655
+ unsubscribe();
3656
+ store.close();
3657
+ });
3658
+ return {
3659
+ error,
3660
+ isLoading,
3661
+ refresh: store.refresh,
3662
+ report,
3663
+ runProof: store.runProof,
3664
+ updatedAt
3665
+ };
3666
+ }
3667
+
3668
+ // src/vue/VoiceTurnLatency.ts
3669
+ var VoiceTurnLatency = defineComponent11({
3670
+ name: "VoiceTurnLatency",
3671
+ props: {
3672
+ class: { default: "", type: String },
3673
+ description: { default: undefined, type: String },
3674
+ intervalMs: { default: 5000, type: Number },
3675
+ path: { default: "/api/turn-latency", type: String },
3676
+ proofLabel: { default: undefined, type: String },
3677
+ proofPath: { default: undefined, type: String },
3678
+ title: { default: undefined, type: String }
3679
+ },
3680
+ setup(props) {
3681
+ const options = {
3682
+ description: props.description,
3683
+ intervalMs: props.intervalMs,
3684
+ proofLabel: props.proofLabel,
3685
+ proofPath: props.proofPath,
3686
+ title: props.title
3687
+ };
3688
+ const latency = useVoiceTurnLatency(props.path, options);
3689
+ const model = computed5(() => createVoiceTurnLatencyViewModel({
3690
+ error: latency.error.value,
3691
+ isLoading: latency.isLoading.value,
3692
+ report: latency.report.value,
3693
+ updatedAt: latency.updatedAt.value
3694
+ }, options));
3695
+ return () => h11("section", {
3696
+ class: [
3697
+ "absolute-voice-turn-latency",
3698
+ `absolute-voice-turn-latency--${model.value.status}`,
3699
+ props.class
3700
+ ]
3701
+ }, [
3702
+ h11("header", { class: "absolute-voice-turn-latency__header" }, [
3703
+ h11("span", { class: "absolute-voice-turn-latency__eyebrow" }, model.value.title),
3704
+ h11("strong", { class: "absolute-voice-turn-latency__label" }, model.value.label)
3705
+ ]),
3706
+ h11("p", { class: "absolute-voice-turn-latency__description" }, model.value.description),
3707
+ model.value.showProofAction ? h11("button", {
3708
+ class: "absolute-voice-turn-latency__proof",
3709
+ onClick: () => {
3710
+ latency.runProof().catch(() => {});
3711
+ },
3712
+ type: "button"
3713
+ }, model.value.proofLabel) : null,
3714
+ model.value.turns.length ? h11("div", { class: "absolute-voice-turn-latency__turns" }, model.value.turns.map((turn) => h11("article", {
3715
+ class: [
3716
+ "absolute-voice-turn-latency__turn",
3717
+ `absolute-voice-turn-latency__turn--${turn.status}`
3718
+ ],
3719
+ key: `${turn.sessionId}:${turn.turnId}`
3720
+ }, [
3721
+ h11("header", [
3722
+ h11("strong", turn.label),
3723
+ h11("span", turn.status)
3724
+ ]),
3725
+ h11("dl", turn.rows.map((row) => h11("div", { key: row.label }, [
3726
+ h11("dt", row.label),
3727
+ h11("dd", row.value)
3728
+ ])))
3729
+ ]))) : h11("p", { class: "absolute-voice-turn-latency__empty" }, "Complete a voice turn to see latency diagnostics."),
3730
+ model.value.error ? h11("p", { class: "absolute-voice-turn-latency__error" }, model.value.error) : null
3731
+ ]);
3732
+ }
3733
+ });
3734
+ // src/vue/VoiceTurnQuality.ts
3735
+ import { computed as computed6, defineComponent as defineComponent12, h as h12 } from "vue";
3736
+
3737
+ // src/client/turnQuality.ts
3738
+ var fetchVoiceTurnQuality = async (path = "/api/turn-quality", options = {}) => {
3739
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3740
+ const response = await fetchImpl(path);
3741
+ if (!response.ok) {
3742
+ throw new Error(`Voice turn quality failed: HTTP ${response.status}`);
3743
+ }
3744
+ return await response.json();
3745
+ };
3746
+ var createVoiceTurnQualityStore = (path = "/api/turn-quality", options = {}) => {
3747
+ const listeners = new Set;
3748
+ let closed = false;
3749
+ let timer;
3750
+ let snapshot = {
3751
+ error: null,
3752
+ isLoading: false
3753
+ };
3754
+ const emit = () => {
3755
+ for (const listener of listeners) {
3756
+ listener();
3757
+ }
3758
+ };
3759
+ const refresh = async () => {
3760
+ if (closed) {
3761
+ return snapshot.report;
3762
+ }
3763
+ snapshot = {
3764
+ ...snapshot,
3765
+ error: null,
3766
+ isLoading: true
3767
+ };
3768
+ emit();
3769
+ try {
3770
+ const report = await fetchVoiceTurnQuality(path, options);
3771
+ snapshot = {
3772
+ error: null,
3773
+ isLoading: false,
3774
+ report,
3775
+ updatedAt: Date.now()
3776
+ };
3777
+ emit();
3778
+ return report;
3779
+ } catch (error) {
3780
+ snapshot = {
3781
+ ...snapshot,
3782
+ error: error instanceof Error ? error.message : String(error),
3783
+ isLoading: false
3784
+ };
3785
+ emit();
3786
+ throw error;
3787
+ }
3788
+ };
3789
+ const close = () => {
3790
+ closed = true;
3791
+ if (timer) {
3792
+ clearInterval(timer);
3793
+ timer = undefined;
3794
+ }
3795
+ listeners.clear();
3796
+ };
3797
+ if (options.intervalMs && options.intervalMs > 0) {
3798
+ timer = setInterval(() => {
3799
+ refresh().catch(() => {});
3800
+ }, options.intervalMs);
3801
+ }
3802
+ return {
3803
+ close,
3804
+ getServerSnapshot: () => snapshot,
3805
+ getSnapshot: () => snapshot,
3806
+ refresh,
3807
+ subscribe: (listener) => {
3808
+ listeners.add(listener);
3809
+ return () => {
3810
+ listeners.delete(listener);
3811
+ };
3812
+ }
3813
+ };
3814
+ };
3815
+
3816
+ // src/client/turnQualityWidget.ts
3817
+ var DEFAULT_TITLE11 = "Turn Quality";
3818
+ var DEFAULT_DESCRIPTION11 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
3819
+ var escapeHtml12 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3820
+ var formatConfidence = (value) => typeof value === "number" ? `${Math.round(value * 100)}%` : "n/a";
3821
+ var formatMaybe = (value) => value === undefined || value === "" ? "n/a" : String(value);
3822
+ var getTurnDetail = (turn) => {
3823
+ if (turn.status === "fail") {
3824
+ return "Empty or unusable committed turn; inspect transcripts and adapter events.";
3825
+ }
3826
+ if (turn.fallbackUsed) {
3827
+ return `Fallback STT selected${turn.fallbackSelectionReason ? ` by ${turn.fallbackSelectionReason}` : ""}.`;
3828
+ }
3829
+ if (turn.correctionChanged) {
3830
+ return `Correction changed the turn${turn.correctionProvider ? ` via ${turn.correctionProvider}` : ""}.`;
3831
+ }
3832
+ if (turn.status === "warn") {
3833
+ return "Turn completed with quality warnings.";
3834
+ }
3835
+ if (turn.status === "unknown") {
3836
+ return "No quality diagnostics were recorded for this turn.";
3837
+ }
3838
+ return "Turn quality looks healthy.";
3839
+ };
3840
+ var createVoiceTurnQualityViewModel = (snapshot, options = {}) => {
3841
+ const turns = (snapshot.report?.turns ?? []).map((turn) => ({
3842
+ ...turn,
3843
+ detail: getTurnDetail(turn),
3844
+ label: turn.text || "Empty turn",
3845
+ rows: [
3846
+ { label: "Source", value: turn.source ?? "unknown" },
3847
+ { label: "Confidence", value: formatConfidence(turn.averageConfidence) },
3848
+ { label: "Fallback", value: turn.fallbackUsed ? "Yes" : "No" },
3849
+ { label: "Correction", value: turn.correctionChanged ? "Changed" : "None" },
3850
+ { label: "Transcripts", value: `${turn.selectedTranscriptCount} selected` },
3851
+ { label: "Cost", value: formatMaybe(turn.costUnits) }
3852
+ ]
3853
+ }));
3854
+ const warningCount = snapshot.report?.warnings ?? turns.filter((turn) => turn.status === "warn").length;
3855
+ const failedCount = snapshot.report?.failed ?? turns.filter((turn) => turn.status === "fail").length;
3856
+ return {
3857
+ description: options.description ?? DEFAULT_DESCRIPTION11,
3858
+ error: snapshot.error,
3859
+ isLoading: snapshot.isLoading,
3860
+ label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} failed` : warningCount > 0 ? `${warningCount} warnings` : `${turns.length} healthy` : snapshot.isLoading ? "Checking" : "No turns",
3861
+ status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
3862
+ title: options.title ?? DEFAULT_TITLE11,
3863
+ turns,
3864
+ updatedAt: snapshot.updatedAt
3865
+ };
3866
+ };
3867
+ var renderVoiceTurnQualityHTML = (snapshot, options = {}) => {
3868
+ const model = createVoiceTurnQualityViewModel(snapshot, options);
3869
+ 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)}">
3870
+ <header>
3871
+ <strong>${escapeHtml12(turn.label)}</strong>
3872
+ <span>${escapeHtml12(turn.status)}</span>
3873
+ </header>
3874
+ <p>${escapeHtml12(turn.detail)}</p>
3875
+ <dl>${turn.rows.map((row) => `<div>
3876
+ <dt>${escapeHtml12(row.label)}</dt>
3877
+ <dd>${escapeHtml12(row.value)}</dd>
3878
+ </div>`).join("")}</dl>
3879
+ </article>`).join("")}</div>` : '<p class="absolute-voice-turn-quality__empty">Complete a voice turn to see STT quality diagnostics.</p>';
3880
+ return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml12(model.status)}">
3881
+ <header class="absolute-voice-turn-quality__header">
3882
+ <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml12(model.title)}</span>
3883
+ <strong class="absolute-voice-turn-quality__label">${escapeHtml12(model.label)}</strong>
3884
+ </header>
3885
+ <p class="absolute-voice-turn-quality__description">${escapeHtml12(model.description)}</p>
3886
+ ${turns}
3887
+ ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml12(model.error)}</p>` : ""}
3888
+ </section>`;
3889
+ };
3890
+ 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}`;
3891
+ var mountVoiceTurnQuality = (element, path = "/api/turn-quality", options = {}) => {
3892
+ const store = createVoiceTurnQualityStore(path, options);
3893
+ const render = () => {
3894
+ element.innerHTML = renderVoiceTurnQualityHTML(store.getSnapshot(), options);
3895
+ };
3896
+ const unsubscribe = store.subscribe(render);
3897
+ render();
3898
+ store.refresh().catch(() => {});
3899
+ return {
3900
+ close: () => {
3901
+ unsubscribe();
3902
+ store.close();
3903
+ },
3904
+ refresh: store.refresh
3905
+ };
3906
+ };
3907
+ var defineVoiceTurnQualityElement = (tagName = "absolute-voice-turn-quality") => {
3908
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
3909
+ return;
3910
+ }
3911
+ customElements.define(tagName, class AbsoluteVoiceTurnQualityElement extends HTMLElement {
3912
+ mounted;
3913
+ connectedCallback() {
3914
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
3915
+ this.mounted = mountVoiceTurnQuality(this, this.getAttribute("path") ?? "/api/turn-quality", {
3916
+ description: this.getAttribute("description") ?? undefined,
3917
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
3918
+ title: this.getAttribute("title") ?? undefined
3919
+ });
3920
+ }
3921
+ disconnectedCallback() {
3922
+ this.mounted?.close();
3923
+ this.mounted = undefined;
3924
+ }
3925
+ });
3926
+ };
3927
+
3928
+ // src/vue/useVoiceTurnQuality.ts
3929
+ import { onUnmounted as onUnmounted13, shallowRef as shallowRef12 } from "vue";
3930
+ function useVoiceTurnQuality(path = "/api/turn-quality", options = {}) {
3931
+ const store = createVoiceTurnQualityStore(path, options);
3932
+ const error = shallowRef12(null);
3933
+ const isLoading = shallowRef12(false);
3934
+ const report = shallowRef12();
3935
+ const updatedAt = shallowRef12(undefined);
3936
+ const sync = () => {
3937
+ const snapshot = store.getSnapshot();
3938
+ error.value = snapshot.error;
3939
+ isLoading.value = snapshot.isLoading;
3940
+ report.value = snapshot.report;
3941
+ updatedAt.value = snapshot.updatedAt;
3942
+ };
3943
+ const unsubscribe = store.subscribe(sync);
3944
+ sync();
3945
+ store.refresh().catch(() => {});
3946
+ onUnmounted13(() => {
3947
+ unsubscribe();
3948
+ store.close();
3949
+ });
3950
+ return { error, isLoading, refresh: store.refresh, report, updatedAt };
3951
+ }
3952
+
3953
+ // src/vue/VoiceTurnQuality.ts
3954
+ var VoiceTurnQuality = defineComponent12({
3955
+ name: "VoiceTurnQuality",
3956
+ props: {
3957
+ class: { default: "", type: String },
3958
+ description: { default: undefined, type: String },
3959
+ intervalMs: { default: 5000, type: Number },
3960
+ path: { default: "/api/turn-quality", type: String },
3961
+ title: { default: undefined, type: String }
3962
+ },
3963
+ setup(props) {
3964
+ const options = {
3965
+ description: props.description,
3966
+ intervalMs: props.intervalMs,
3967
+ title: props.title
3968
+ };
3969
+ const quality = useVoiceTurnQuality(props.path, options);
3970
+ const model = computed6(() => createVoiceTurnQualityViewModel({
3971
+ error: quality.error.value,
3972
+ isLoading: quality.isLoading.value,
3973
+ report: quality.report.value,
3974
+ updatedAt: quality.updatedAt.value
3975
+ }, options));
3976
+ return () => h12("section", {
3977
+ class: [
3978
+ "absolute-voice-turn-quality",
3979
+ `absolute-voice-turn-quality--${model.value.status}`,
3980
+ props.class
3981
+ ]
3982
+ }, [
3983
+ h12("header", { class: "absolute-voice-turn-quality__header" }, [
3984
+ h12("span", { class: "absolute-voice-turn-quality__eyebrow" }, model.value.title),
3985
+ h12("strong", { class: "absolute-voice-turn-quality__label" }, model.value.label)
3986
+ ]),
3987
+ h12("p", { class: "absolute-voice-turn-quality__description" }, model.value.description),
3988
+ model.value.turns.length ? h12("div", { class: "absolute-voice-turn-quality__turns" }, model.value.turns.map((turn) => h12("article", {
3989
+ class: [
3990
+ "absolute-voice-turn-quality__turn",
3991
+ `absolute-voice-turn-quality__turn--${turn.status}`
3992
+ ],
3993
+ key: `${turn.sessionId}:${turn.turnId}`
3994
+ }, [
3995
+ h12("header", [
3996
+ h12("strong", turn.label),
3997
+ h12("span", turn.status)
3998
+ ]),
3999
+ h12("p", turn.detail),
4000
+ h12("dl", turn.rows.map((row) => h12("div", { key: row.label }, [
4001
+ h12("dt", row.label),
4002
+ h12("dd", row.value)
4003
+ ])))
4004
+ ]))) : h12("p", { class: "absolute-voice-turn-quality__empty" }, "Complete a voice turn to see STT quality diagnostics."),
4005
+ model.value.error ? h12("p", { class: "absolute-voice-turn-quality__error" }, model.value.error) : null
4006
+ ]);
4007
+ }
4008
+ });
4009
+ // src/vue/useVoiceLiveOps.ts
4010
+ import { onUnmounted as onUnmounted14, ref as ref10, shallowRef as shallowRef13 } from "vue";
4011
+
4012
+ // src/client/liveOps.ts
4013
+ var postVoiceLiveOpsAction = async (input, options = {}) => {
4014
+ if (!input.sessionId) {
4015
+ throw new Error("Start a voice session before running live ops actions.");
4016
+ }
4017
+ const fetchImpl = options.fetch ?? globalThis.fetch;
4018
+ const response = await fetchImpl(options.actionPath ?? "/api/voice/live-ops/action", {
4019
+ body: JSON.stringify(input),
4020
+ headers: {
4021
+ "Content-Type": "application/json"
4022
+ },
4023
+ method: "POST"
4024
+ });
4025
+ const payload = await response.json().catch(() => null);
4026
+ if (!response.ok || !payload?.ok) {
4027
+ const message = payload && typeof payload === "object" && "error" in payload ? String(payload.error) : `Voice live ops action failed: HTTP ${response.status}`;
4028
+ throw new Error(message);
4029
+ }
4030
+ return payload;
4031
+ };
4032
+ var createVoiceLiveOpsStore = (options = {}) => {
4033
+ const listeners = new Set;
4034
+ let closed = false;
4035
+ let snapshot = {
4036
+ error: null,
4037
+ isRunning: false
4038
+ };
4039
+ const emit = () => {
4040
+ for (const listener of listeners) {
4041
+ listener();
4042
+ }
4043
+ };
4044
+ const run = async (input) => {
4045
+ if (closed) {
4046
+ return snapshot.lastResult;
4047
+ }
4048
+ snapshot = {
4049
+ ...snapshot,
4050
+ error: null,
4051
+ isRunning: true,
4052
+ runningAction: input.action
4053
+ };
4054
+ emit();
4055
+ try {
4056
+ const result = await postVoiceLiveOpsAction(input, options);
4057
+ await options.onControl?.(result);
4058
+ snapshot = {
4059
+ ...snapshot,
4060
+ error: null,
4061
+ isRunning: false,
4062
+ lastResult: result,
4063
+ runningAction: undefined,
4064
+ updatedAt: Date.now()
4065
+ };
4066
+ emit();
4067
+ return result;
4068
+ } catch (error) {
4069
+ snapshot = {
4070
+ ...snapshot,
4071
+ error: error instanceof Error ? error.message : String(error),
4072
+ isRunning: false,
4073
+ runningAction: undefined,
4074
+ updatedAt: Date.now()
4075
+ };
4076
+ emit();
4077
+ throw error;
4078
+ }
4079
+ };
4080
+ const close = () => {
4081
+ closed = true;
4082
+ listeners.clear();
4083
+ };
4084
+ return {
4085
+ close,
4086
+ getServerSnapshot: () => snapshot,
4087
+ getSnapshot: () => snapshot,
4088
+ run,
4089
+ subscribe: (listener) => {
4090
+ listeners.add(listener);
4091
+ return () => {
4092
+ listeners.delete(listener);
4093
+ };
4094
+ }
4095
+ };
4096
+ };
4097
+
4098
+ // src/vue/useVoiceLiveOps.ts
4099
+ function useVoiceLiveOps(options = {}) {
4100
+ const store = createVoiceLiveOpsStore(options);
4101
+ const error = ref10(null);
4102
+ const isRunning = ref10(false);
4103
+ const lastResult = shallowRef13(undefined);
4104
+ const runningAction = ref10(undefined);
4105
+ const updatedAt = ref10(undefined);
4106
+ const sync = () => {
4107
+ const snapshot = store.getSnapshot();
4108
+ error.value = snapshot.error;
4109
+ isRunning.value = snapshot.isRunning;
4110
+ lastResult.value = snapshot.lastResult;
4111
+ runningAction.value = snapshot.runningAction;
4112
+ updatedAt.value = snapshot.updatedAt;
4113
+ };
4114
+ const unsubscribe = store.subscribe(sync);
4115
+ sync();
4116
+ onUnmounted14(() => {
4117
+ unsubscribe();
4118
+ store.close();
4119
+ });
4120
+ return {
4121
+ error,
4122
+ isRunning,
4123
+ lastResult,
4124
+ run: store.run,
4125
+ runningAction,
4126
+ updatedAt
4127
+ };
4128
+ }
4129
+ // src/vue/useVoiceCampaignDialerProof.ts
4130
+ import { onUnmounted as onUnmounted15, shallowRef as shallowRef14 } from "vue";
4131
+
4132
+ // src/client/campaignDialerProof.ts
4133
+ var fetchVoiceCampaignDialerProofStatus = async (path = "/api/voice/campaigns/dialer-proof", options = {}) => {
4134
+ const fetchImpl = options.fetch ?? globalThis.fetch;
4135
+ const response = await fetchImpl(path);
4136
+ if (!response.ok) {
4137
+ throw new Error(`Voice campaign dialer proof status failed: HTTP ${response.status}`);
4138
+ }
4139
+ return await response.json();
4140
+ };
4141
+ var runVoiceCampaignDialerProofAction = async (path = "/api/voice/campaigns/dialer-proof", options = {}) => {
4142
+ const fetchImpl = options.fetch ?? globalThis.fetch;
4143
+ const response = await fetchImpl(path, { method: "POST" });
4144
+ if (!response.ok) {
4145
+ throw new Error(`Voice campaign dialer proof failed: HTTP ${response.status}`);
4146
+ }
4147
+ return await response.json();
4148
+ };
4149
+ var createVoiceCampaignDialerProofStore = (path = "/api/voice/campaigns/dialer-proof", options = {}) => {
4150
+ const listeners = new Set;
4151
+ let closed = false;
4152
+ let timer;
4153
+ let snapshot = {
4154
+ error: null,
4155
+ isLoading: false
4156
+ };
4157
+ const emit = () => {
4158
+ for (const listener of listeners) {
4159
+ listener();
4160
+ }
4161
+ };
4162
+ const refresh = async () => {
4163
+ if (closed) {
4164
+ return snapshot.status;
4165
+ }
4166
+ snapshot = { ...snapshot, error: null, isLoading: true };
4167
+ emit();
4168
+ try {
4169
+ const status = await fetchVoiceCampaignDialerProofStatus(path, options);
4170
+ snapshot = {
4171
+ ...snapshot,
4172
+ error: null,
4173
+ isLoading: false,
4174
+ status,
4175
+ updatedAt: Date.now()
4176
+ };
4177
+ emit();
4178
+ return status;
4179
+ } catch (error) {
4180
+ snapshot = {
4181
+ ...snapshot,
4182
+ error: error instanceof Error ? error.message : String(error),
4183
+ isLoading: false
4184
+ };
4185
+ emit();
4186
+ throw error;
4187
+ }
4188
+ };
4189
+ const runProof = async () => {
4190
+ const runPath = options.runPath ?? snapshot.status?.runPath ?? path;
4191
+ snapshot = { ...snapshot, error: null, isLoading: true };
4192
+ emit();
4193
+ try {
4194
+ const report = await runVoiceCampaignDialerProofAction(runPath, options);
4195
+ snapshot = {
4196
+ ...snapshot,
4197
+ error: null,
4198
+ isLoading: false,
4199
+ report,
4200
+ status: {
4201
+ generatedAt: Date.now(),
4202
+ mode: report.mode,
4203
+ ok: report.ok,
4204
+ providers: report.providers.map((provider) => provider.provider),
4205
+ runPath,
4206
+ safe: true
4207
+ },
4208
+ updatedAt: Date.now()
4209
+ };
4210
+ emit();
4211
+ return report;
4212
+ } catch (error) {
4213
+ snapshot = {
4214
+ ...snapshot,
4215
+ error: error instanceof Error ? error.message : String(error),
4216
+ isLoading: false
4217
+ };
4218
+ emit();
4219
+ throw error;
4220
+ }
4221
+ };
4222
+ const close = () => {
4223
+ closed = true;
4224
+ if (timer) {
4225
+ clearInterval(timer);
4226
+ timer = undefined;
4227
+ }
4228
+ listeners.clear();
4229
+ };
4230
+ if (options.intervalMs && options.intervalMs > 0) {
4231
+ timer = setInterval(() => {
4232
+ refresh().catch(() => {});
4233
+ }, options.intervalMs);
4234
+ }
4235
+ return {
4236
+ close,
4237
+ getServerSnapshot: () => snapshot,
4238
+ getSnapshot: () => snapshot,
4239
+ refresh,
4240
+ runProof,
4241
+ subscribe: (listener) => {
4242
+ listeners.add(listener);
4243
+ return () => {
4244
+ listeners.delete(listener);
4245
+ };
4246
+ }
4247
+ };
4248
+ };
4249
+
4250
+ // src/vue/useVoiceCampaignDialerProof.ts
4251
+ function useVoiceCampaignDialerProof(path = "/api/voice/campaigns/dialer-proof", options = {}) {
4252
+ const store = createVoiceCampaignDialerProofStore(path, options);
4253
+ const error = shallowRef14(null);
4254
+ const isLoading = shallowRef14(false);
4255
+ const report = shallowRef14();
4256
+ const status = shallowRef14();
4257
+ const updatedAt = shallowRef14(undefined);
4258
+ const sync = () => {
4259
+ const snapshot = store.getSnapshot();
4260
+ error.value = snapshot.error;
4261
+ isLoading.value = snapshot.isLoading;
4262
+ report.value = snapshot.report;
4263
+ status.value = snapshot.status;
4264
+ updatedAt.value = snapshot.updatedAt;
4265
+ };
4266
+ const unsubscribe = store.subscribe(sync);
4267
+ sync();
4268
+ if (typeof window !== "undefined") {
4269
+ store.refresh().catch(() => {});
4270
+ }
4271
+ onUnmounted15(() => {
4272
+ unsubscribe();
4273
+ store.close();
4274
+ });
4275
+ return {
4276
+ error,
4277
+ isLoading,
4278
+ refresh: store.refresh,
4279
+ report,
4280
+ runProof: store.runProof,
4281
+ status,
4282
+ updatedAt
4283
+ };
4284
+ }
72
4285
  // src/vue/useVoiceStream.ts
73
- import { onUnmounted, ref, shallowRef } from "vue";
4286
+ import { onUnmounted as onUnmounted16, ref as ref11, shallowRef as shallowRef15 } from "vue";
74
4287
 
75
4288
  // src/client/actions.ts
76
4289
  var normalizeErrorMessage = (value) => {
@@ -120,6 +4333,17 @@ var serverMessageToAction = (message) => {
120
4333
  sessionId: message.sessionId,
121
4334
  type: "complete"
122
4335
  };
4336
+ case "connection":
4337
+ return {
4338
+ reconnect: message.reconnect,
4339
+ type: "connection"
4340
+ };
4341
+ case "call_lifecycle":
4342
+ return {
4343
+ event: message.event,
4344
+ sessionId: message.sessionId,
4345
+ type: "call_lifecycle"
4346
+ };
123
4347
  case "error":
124
4348
  return {
125
4349
  message: normalizeErrorMessage(message.message),
@@ -135,6 +4359,17 @@ var serverMessageToAction = (message) => {
135
4359
  transcript: message.transcript,
136
4360
  type: "partial"
137
4361
  };
4362
+ case "replay":
4363
+ return {
4364
+ assistantTexts: message.assistantTexts,
4365
+ call: message.call,
4366
+ partial: message.partial,
4367
+ scenarioId: message.scenarioId,
4368
+ sessionId: message.sessionId,
4369
+ status: message.status,
4370
+ turns: message.turns,
4371
+ type: "replay"
4372
+ };
138
4373
  case "session":
139
4374
  return {
140
4375
  sessionId: message.sessionId,
@@ -163,7 +4398,7 @@ var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
163
4398
  var noop = () => {};
164
4399
  var noopUnsubscribe = () => noop;
165
4400
  var NOOP_CONNECTION = {
166
- start: () => {},
4401
+ callControl: noop,
167
4402
  close: noop,
168
4403
  endTurn: noop,
169
4404
  getReadyState: () => WS_CLOSED,
@@ -171,6 +4406,7 @@ var NOOP_CONNECTION = {
171
4406
  getSessionId: () => "",
172
4407
  send: noop,
173
4408
  sendAudio: noop,
4409
+ start: () => {},
174
4410
  subscribe: noopUnsubscribe
175
4411
  };
176
4412
  var createSessionId = () => crypto.randomUUID();
@@ -192,11 +4428,14 @@ var isVoiceServerMessage = (value) => {
192
4428
  switch (value.type) {
193
4429
  case "audio":
194
4430
  case "assistant":
4431
+ case "call_lifecycle":
195
4432
  case "complete":
4433
+ case "connection":
196
4434
  case "error":
197
4435
  case "final":
198
4436
  case "partial":
199
4437
  case "pong":
4438
+ case "replay":
200
4439
  case "session":
201
4440
  case "turn":
202
4441
  return true;
@@ -233,6 +4472,9 @@ var createVoiceConnection = (path, options = {}) => {
233
4472
  sessionId: options.sessionId ?? createSessionId(),
234
4473
  ws: null
235
4474
  };
4475
+ const emitConnection = (reconnect) => {
4476
+ listeners.forEach((listener) => listener(reconnect));
4477
+ };
236
4478
  const clearTimers = () => {
237
4479
  if (state.pingInterval) {
238
4480
  clearInterval(state.pingInterval);
@@ -255,9 +4497,28 @@ var createVoiceConnection = (path, options = {}) => {
255
4497
  }
256
4498
  };
257
4499
  const scheduleReconnect = () => {
4500
+ const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
258
4501
  state.reconnectAttempts += 1;
4502
+ emitConnection({
4503
+ reconnect: {
4504
+ attempts: state.reconnectAttempts,
4505
+ lastDisconnectAt: Date.now(),
4506
+ maxAttempts: maxReconnectAttempts,
4507
+ nextAttemptAt,
4508
+ status: "reconnecting"
4509
+ },
4510
+ type: "connection"
4511
+ });
259
4512
  state.reconnectTimeout = setTimeout(() => {
260
4513
  if (state.reconnectAttempts > maxReconnectAttempts) {
4514
+ emitConnection({
4515
+ reconnect: {
4516
+ attempts: state.reconnectAttempts,
4517
+ maxAttempts: maxReconnectAttempts,
4518
+ status: "exhausted"
4519
+ },
4520
+ type: "connection"
4521
+ });
261
4522
  return;
262
4523
  }
263
4524
  connect();
@@ -267,9 +4528,21 @@ var createVoiceConnection = (path, options = {}) => {
267
4528
  const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
268
4529
  ws.binaryType = "arraybuffer";
269
4530
  ws.onopen = () => {
4531
+ const wasReconnecting = state.reconnectAttempts > 0;
270
4532
  state.isConnected = true;
271
- state.reconnectAttempts = 0;
272
4533
  flushPendingMessages();
4534
+ if (wasReconnecting) {
4535
+ emitConnection({
4536
+ reconnect: {
4537
+ attempts: state.reconnectAttempts,
4538
+ lastResumedAt: Date.now(),
4539
+ maxAttempts: maxReconnectAttempts,
4540
+ status: "resumed"
4541
+ },
4542
+ type: "connection"
4543
+ });
4544
+ state.reconnectAttempts = 0;
4545
+ }
273
4546
  listeners.forEach((listener) => listener({
274
4547
  scenarioId: state.scenarioId ?? undefined,
275
4548
  sessionId: state.sessionId,
@@ -299,6 +4572,16 @@ var createVoiceConnection = (path, options = {}) => {
299
4572
  const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
300
4573
  if (reconnectable) {
301
4574
  scheduleReconnect();
4575
+ } else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
4576
+ emitConnection({
4577
+ reconnect: {
4578
+ attempts: state.reconnectAttempts,
4579
+ lastDisconnectAt: Date.now(),
4580
+ maxAttempts: maxReconnectAttempts,
4581
+ status: "exhausted"
4582
+ },
4583
+ type: "connection"
4584
+ });
302
4585
  }
303
4586
  };
304
4587
  state.ws = ws;
@@ -332,6 +4615,12 @@ var createVoiceConnection = (path, options = {}) => {
332
4615
  const endTurn = () => {
333
4616
  send({ type: "end_turn" });
334
4617
  };
4618
+ const callControl = (message) => {
4619
+ send({
4620
+ ...message,
4621
+ type: "call_control"
4622
+ });
4623
+ };
335
4624
  const close = () => {
336
4625
  clearTimers();
337
4626
  if (state.ws) {
@@ -349,7 +4638,7 @@ var createVoiceConnection = (path, options = {}) => {
349
4638
  };
350
4639
  connect();
351
4640
  return {
352
- start,
4641
+ callControl,
353
4642
  close,
354
4643
  endTurn,
355
4644
  getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
@@ -357,18 +4646,26 @@ var createVoiceConnection = (path, options = {}) => {
357
4646
  getSessionId: () => state.sessionId,
358
4647
  send,
359
4648
  sendAudio,
4649
+ start,
360
4650
  subscribe
361
4651
  };
362
4652
  };
363
4653
 
364
4654
  // src/client/store.ts
4655
+ var createInitialReconnectState = () => ({
4656
+ attempts: 0,
4657
+ maxAttempts: 0,
4658
+ status: "idle"
4659
+ });
365
4660
  var createInitialState = () => ({
366
4661
  assistantAudio: [],
367
4662
  assistantTexts: [],
4663
+ call: null,
368
4664
  error: null,
369
4665
  isConnected: false,
370
4666
  scenarioId: null,
371
4667
  partial: "",
4668
+ reconnect: createInitialReconnectState(),
372
4669
  sessionId: null,
373
4670
  status: "idle",
374
4671
  turns: []
@@ -408,10 +4705,36 @@ var createVoiceStreamStore = () => {
408
4705
  status: "completed"
409
4706
  };
410
4707
  break;
4708
+ case "call_lifecycle":
4709
+ state = {
4710
+ ...state,
4711
+ call: {
4712
+ ...state.call,
4713
+ disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
4714
+ endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
4715
+ events: [...state.call?.events ?? [], action.event],
4716
+ lastEventAt: action.event.at,
4717
+ startedAt: state.call?.startedAt ?? action.event.at
4718
+ },
4719
+ sessionId: action.sessionId
4720
+ };
4721
+ break;
411
4722
  case "connected":
412
4723
  state = {
413
4724
  ...state,
414
- isConnected: true
4725
+ isConnected: true,
4726
+ reconnect: state.reconnect.status === "reconnecting" ? {
4727
+ ...state.reconnect,
4728
+ lastResumedAt: Date.now(),
4729
+ nextAttemptAt: undefined,
4730
+ status: "resumed"
4731
+ } : state.reconnect
4732
+ };
4733
+ break;
4734
+ case "connection":
4735
+ state = {
4736
+ ...state,
4737
+ reconnect: action.reconnect
415
4738
  };
416
4739
  break;
417
4740
  case "disconnected":
@@ -439,6 +4762,26 @@ var createVoiceStreamStore = () => {
439
4762
  partial: action.transcript.text
440
4763
  };
441
4764
  break;
4765
+ case "replay":
4766
+ state = {
4767
+ ...state,
4768
+ assistantTexts: [...action.assistantTexts],
4769
+ call: action.call ?? null,
4770
+ error: null,
4771
+ isConnected: action.status === "active",
4772
+ partial: action.partial,
4773
+ reconnect: state.reconnect.status === "reconnecting" ? {
4774
+ ...state.reconnect,
4775
+ lastResumedAt: Date.now(),
4776
+ nextAttemptAt: undefined,
4777
+ status: "resumed"
4778
+ } : state.reconnect,
4779
+ scenarioId: action.scenarioId ?? state.scenarioId,
4780
+ sessionId: action.sessionId,
4781
+ status: action.status,
4782
+ turns: [...action.turns]
4783
+ };
4784
+ break;
442
4785
  case "session":
443
4786
  state = {
444
4787
  ...state,
@@ -486,14 +4829,41 @@ var createVoiceStream = (path, options = {}) => {
486
4829
  const notify = () => {
487
4830
  subscribers.forEach((subscriber) => subscriber());
488
4831
  };
4832
+ const reportReconnect = () => {
4833
+ if (!options.reconnectReportPath || typeof fetch === "undefined") {
4834
+ return;
4835
+ }
4836
+ const snapshot = store.getSnapshot();
4837
+ const body = JSON.stringify({
4838
+ at: Date.now(),
4839
+ reconnect: snapshot.reconnect,
4840
+ scenarioId: snapshot.scenarioId,
4841
+ sessionId: connection.getSessionId(),
4842
+ turnIds: snapshot.turns.map((turn) => turn.id)
4843
+ });
4844
+ fetch(options.reconnectReportPath, {
4845
+ body,
4846
+ headers: {
4847
+ "Content-Type": "application/json"
4848
+ },
4849
+ keepalive: true,
4850
+ method: "POST"
4851
+ }).catch(() => {});
4852
+ };
489
4853
  const unsubscribeConnection = connection.subscribe((message) => {
490
4854
  const action = serverMessageToAction(message);
491
4855
  if (action) {
492
4856
  store.dispatch(action);
4857
+ if (message.type === "connection") {
4858
+ reportReconnect();
4859
+ }
493
4860
  notify();
494
4861
  }
495
4862
  });
496
4863
  return {
4864
+ callControl(message) {
4865
+ connection.callControl(message);
4866
+ },
497
4867
  close() {
498
4868
  unsubscribeConnection();
499
4869
  connection.close();
@@ -522,6 +4892,9 @@ var createVoiceStream = (path, options = {}) => {
522
4892
  get partial() {
523
4893
  return store.getSnapshot().partial;
524
4894
  },
4895
+ get reconnect() {
4896
+ return store.getSnapshot().reconnect;
4897
+ },
525
4898
  get sessionId() {
526
4899
  return connection.getSessionId();
527
4900
  },
@@ -537,6 +4910,9 @@ var createVoiceStream = (path, options = {}) => {
537
4910
  get assistantAudio() {
538
4911
  return store.getSnapshot().assistantAudio;
539
4912
  },
4913
+ get call() {
4914
+ return store.getSnapshot().call;
4915
+ },
540
4916
  sendAudio(audio) {
541
4917
  connection.sendAudio(audio);
542
4918
  },
@@ -550,22 +4926,26 @@ var createVoiceStream = (path, options = {}) => {
550
4926
  };
551
4927
 
552
4928
  // src/vue/useVoiceStream.ts
553
- var useVoiceStream = (path, options = {}) => {
4929
+ function useVoiceStream(path, options = {}) {
554
4930
  const stream = createVoiceStream(path, options);
555
- const assistantAudio = shallowRef([]);
556
- const assistantTexts = shallowRef([]);
557
- const error = ref(null);
558
- const isConnected = ref(false);
559
- const partial = ref("");
560
- const sessionId = ref(stream.sessionId);
561
- const status = ref(stream.status);
562
- const turns = shallowRef([]);
4931
+ const assistantAudio = shallowRef15([]);
4932
+ const assistantTexts = shallowRef15([]);
4933
+ const call = shallowRef15(null);
4934
+ const error = ref11(null);
4935
+ const isConnected = ref11(false);
4936
+ const partial = ref11("");
4937
+ const reconnect = shallowRef15(stream.reconnect);
4938
+ const sessionId = ref11(stream.sessionId);
4939
+ const status = ref11(stream.status);
4940
+ const turns = shallowRef15([]);
563
4941
  const sync = () => {
564
4942
  assistantAudio.value = [...stream.assistantAudio];
565
4943
  assistantTexts.value = [...stream.assistantTexts];
4944
+ call.value = stream.call;
566
4945
  error.value = stream.error;
567
4946
  isConnected.value = stream.isConnected;
568
4947
  partial.value = stream.partial;
4948
+ reconnect.value = stream.reconnect;
569
4949
  sessionId.value = stream.sessionId;
570
4950
  status.value = stream.status;
571
4951
  turns.value = [...stream.turns];
@@ -576,23 +4956,26 @@ var useVoiceStream = (path, options = {}) => {
576
4956
  unsubscribe();
577
4957
  stream.close();
578
4958
  };
579
- onUnmounted(destroy);
4959
+ onUnmounted16(destroy);
580
4960
  return {
581
4961
  assistantAudio,
582
4962
  assistantTexts,
4963
+ call,
4964
+ callControl: (message) => stream.callControl(message),
583
4965
  close: () => destroy(),
584
4966
  endTurn: () => stream.endTurn(),
585
4967
  error,
586
4968
  isConnected,
587
4969
  partial,
4970
+ reconnect,
588
4971
  sendAudio: (audio) => stream.sendAudio(audio),
589
4972
  sessionId,
590
4973
  status,
591
4974
  turns
592
4975
  };
593
- };
4976
+ }
594
4977
  // src/vue/useVoiceController.ts
595
- import { onUnmounted as onUnmounted2, ref as ref2, shallowRef as shallowRef2 } from "vue";
4978
+ import { onUnmounted as onUnmounted17, ref as ref12, shallowRef as shallowRef16 } from "vue";
596
4979
 
597
4980
  // src/client/htmx.ts
598
4981
  var DEFAULT_EVENT_NAME = "voice-refresh";
@@ -1056,10 +5439,12 @@ var resolveVoiceRuntimePreset = (name = "default") => {
1056
5439
  var createInitialState2 = (stream) => ({
1057
5440
  assistantAudio: [...stream.assistantAudio],
1058
5441
  assistantTexts: [...stream.assistantTexts],
5442
+ call: stream.call,
1059
5443
  error: stream.error,
1060
5444
  isConnected: stream.isConnected,
1061
5445
  isRecording: false,
1062
5446
  partial: stream.partial,
5447
+ reconnect: stream.reconnect,
1063
5448
  recordingError: null,
1064
5449
  sessionId: stream.sessionId,
1065
5450
  scenarioId: stream.scenarioId,
@@ -1085,9 +5470,11 @@ var createVoiceController = (path, options = {}) => {
1085
5470
  ...state,
1086
5471
  assistantAudio: [...stream.assistantAudio],
1087
5472
  assistantTexts: [...stream.assistantTexts],
5473
+ call: stream.call,
1088
5474
  error: stream.error,
1089
5475
  isConnected: stream.isConnected,
1090
5476
  partial: stream.partial,
5477
+ reconnect: stream.reconnect,
1091
5478
  sessionId: stream.sessionId,
1092
5479
  scenarioId: stream.scenarioId,
1093
5480
  status: stream.status,
@@ -1112,7 +5499,13 @@ var createVoiceController = (path, options = {}) => {
1112
5499
  capture = createMicrophoneCapture({
1113
5500
  channelCount: options.capture?.channelCount ?? preset.capture.channelCount,
1114
5501
  onLevel: options.capture?.onLevel,
1115
- onAudio: (audio) => stream.sendAudio(audio),
5502
+ onAudio: (audio) => {
5503
+ if (options.capture?.onAudio) {
5504
+ options.capture.onAudio(audio, stream.sendAudio);
5505
+ return;
5506
+ }
5507
+ stream.sendAudio(audio);
5508
+ },
1116
5509
  sampleRateHz: options.capture?.sampleRateHz ?? preset.capture.sampleRateHz
1117
5510
  });
1118
5511
  return capture;
@@ -1162,6 +5555,7 @@ var createVoiceController = (path, options = {}) => {
1162
5555
  bindHTMX(bindingOptions) {
1163
5556
  return bindVoiceHTMX(stream, bindingOptions);
1164
5557
  },
5558
+ callControl: (message) => stream.callControl(message),
1165
5559
  close,
1166
5560
  endTurn: () => stream.endTurn(),
1167
5561
  get error() {
@@ -1181,6 +5575,9 @@ var createVoiceController = (path, options = {}) => {
1181
5575
  get recordingError() {
1182
5576
  return state.recordingError;
1183
5577
  },
5578
+ get reconnect() {
5579
+ return state.reconnect;
5580
+ },
1184
5581
  sendAudio: (audio) => stream.sendAudio(audio),
1185
5582
  get sessionId() {
1186
5583
  return state.sessionId;
@@ -1214,23 +5611,27 @@ var createVoiceController = (path, options = {}) => {
1214
5611
  },
1215
5612
  get assistantAudio() {
1216
5613
  return state.assistantAudio;
5614
+ },
5615
+ get call() {
5616
+ return state.call;
1217
5617
  }
1218
5618
  };
1219
5619
  };
1220
5620
 
1221
5621
  // src/vue/useVoiceController.ts
1222
- var useVoiceController = (path, options = {}) => {
5622
+ function useVoiceController(path, options = {}) {
1223
5623
  const controller = createVoiceController(path, options);
1224
- const assistantAudio = shallowRef2([]);
1225
- const assistantTexts = shallowRef2([]);
1226
- const error = ref2(null);
1227
- const isConnected = ref2(false);
1228
- const isRecording = ref2(false);
1229
- const partial = ref2("");
1230
- const recordingError = ref2(null);
1231
- const sessionId = ref2(controller.sessionId);
1232
- const status = ref2(controller.status);
1233
- const turns = shallowRef2([]);
5624
+ const assistantAudio = shallowRef16([]);
5625
+ const assistantTexts = shallowRef16([]);
5626
+ const error = ref12(null);
5627
+ const isConnected = ref12(false);
5628
+ const isRecording = ref12(false);
5629
+ const partial = ref12("");
5630
+ const reconnect = shallowRef16(controller.reconnect);
5631
+ const recordingError = ref12(null);
5632
+ const sessionId = ref12(controller.sessionId);
5633
+ const status = ref12(controller.status);
5634
+ const turns = shallowRef16([]);
1234
5635
  const sync = () => {
1235
5636
  assistantAudio.value = [...controller.assistantAudio];
1236
5637
  assistantTexts.value = [...controller.assistantTexts];
@@ -1238,6 +5639,7 @@ var useVoiceController = (path, options = {}) => {
1238
5639
  isConnected.value = controller.isConnected;
1239
5640
  isRecording.value = controller.isRecording;
1240
5641
  partial.value = controller.partial;
5642
+ reconnect.value = controller.reconnect;
1241
5643
  recordingError.value = controller.recordingError;
1242
5644
  sessionId.value = controller.sessionId;
1243
5645
  status.value = controller.status;
@@ -1249,7 +5651,7 @@ var useVoiceController = (path, options = {}) => {
1249
5651
  unsubscribe();
1250
5652
  controller.close();
1251
5653
  };
1252
- onUnmounted2(destroy);
5654
+ onUnmounted17(destroy);
1253
5655
  return {
1254
5656
  assistantAudio,
1255
5657
  assistantTexts,
@@ -1260,6 +5662,7 @@ var useVoiceController = (path, options = {}) => {
1260
5662
  isConnected,
1261
5663
  isRecording,
1262
5664
  partial,
5665
+ reconnect,
1263
5666
  recordingError,
1264
5667
  sendAudio: (audio) => controller.sendAudio(audio),
1265
5668
  sessionId,
@@ -1269,27 +5672,56 @@ var useVoiceController = (path, options = {}) => {
1269
5672
  toggleRecording: () => controller.toggleRecording(),
1270
5673
  turns
1271
5674
  };
1272
- };
1273
- // src/vue/useVoiceProviderStatus.ts
1274
- import { onUnmounted as onUnmounted3, ref as ref3, shallowRef as shallowRef3 } from "vue";
5675
+ }
5676
+ // src/vue/useVoiceTraceTimeline.ts
5677
+ import { onUnmounted as onUnmounted18, ref as ref13, shallowRef as shallowRef17 } from "vue";
5678
+ function useVoiceTraceTimeline(path = "/api/voice-traces", options = {}) {
5679
+ const store = createVoiceTraceTimelineStore(path, options);
5680
+ const error = ref13(null);
5681
+ const isLoading = ref13(false);
5682
+ const report = shallowRef17(null);
5683
+ const updatedAt = ref13(undefined);
5684
+ const sync = () => {
5685
+ const snapshot = store.getSnapshot();
5686
+ error.value = snapshot.error;
5687
+ isLoading.value = snapshot.isLoading;
5688
+ report.value = snapshot.report;
5689
+ updatedAt.value = snapshot.updatedAt;
5690
+ };
5691
+ const unsubscribe = store.subscribe(sync);
5692
+ sync();
5693
+ store.refresh().catch(() => {});
5694
+ onUnmounted18(() => {
5695
+ unsubscribe();
5696
+ store.close();
5697
+ });
5698
+ return {
5699
+ error,
5700
+ isLoading,
5701
+ refresh: store.refresh,
5702
+ report,
5703
+ updatedAt
5704
+ };
5705
+ }
5706
+ // src/vue/useVoiceWorkflowStatus.ts
5707
+ import { onUnmounted as onUnmounted19, ref as ref14, shallowRef as shallowRef18 } from "vue";
1275
5708
 
1276
- // src/client/providerStatus.ts
1277
- var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
5709
+ // src/client/workflowStatus.ts
5710
+ var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
1278
5711
  const fetchImpl = options.fetch ?? globalThis.fetch;
1279
5712
  const response = await fetchImpl(path);
1280
5713
  if (!response.ok) {
1281
- throw new Error(`Voice provider status failed: HTTP ${response.status}`);
5714
+ throw new Error(`Voice workflow status failed: HTTP ${response.status}`);
1282
5715
  }
1283
5716
  return await response.json();
1284
5717
  };
1285
- var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
5718
+ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options = {}) => {
1286
5719
  const listeners = new Set;
1287
5720
  let closed = false;
1288
5721
  let timer;
1289
5722
  let snapshot = {
1290
5723
  error: null,
1291
- isLoading: false,
1292
- providers: []
5724
+ isLoading: false
1293
5725
  };
1294
5726
  const emit = () => {
1295
5727
  for (const listener of listeners) {
@@ -1298,7 +5730,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1298
5730
  };
1299
5731
  const refresh = async () => {
1300
5732
  if (closed) {
1301
- return snapshot.providers;
5733
+ return snapshot.report;
1302
5734
  }
1303
5735
  snapshot = {
1304
5736
  ...snapshot,
@@ -1307,15 +5739,15 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1307
5739
  };
1308
5740
  emit();
1309
5741
  try {
1310
- const providers = await fetchVoiceProviderStatus(path, options);
5742
+ const report = await fetchVoiceWorkflowStatus(path, options);
1311
5743
  snapshot = {
1312
5744
  error: null,
1313
5745
  isLoading: false,
1314
- providers,
5746
+ report,
1315
5747
  updatedAt: Date.now()
1316
5748
  };
1317
5749
  emit();
1318
- return providers;
5750
+ return report;
1319
5751
  } catch (error) {
1320
5752
  snapshot = {
1321
5753
  ...snapshot,
@@ -1334,7 +5766,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1334
5766
  }
1335
5767
  listeners.clear();
1336
5768
  };
1337
- if (options.intervalMs && options.intervalMs > 0) {
5769
+ if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
1338
5770
  timer = setInterval(() => {
1339
5771
  refresh().catch(() => {});
1340
5772
  }, options.intervalMs);
@@ -1353,37 +5785,67 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1353
5785
  };
1354
5786
  };
1355
5787
 
1356
- // src/vue/useVoiceProviderStatus.ts
1357
- var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
1358
- const store = createVoiceProviderStatusStore(path, options);
1359
- const error = ref3(null);
1360
- const isLoading = ref3(false);
1361
- const providers = shallowRef3([]);
1362
- const updatedAt = ref3(undefined);
5788
+ // src/vue/useVoiceWorkflowStatus.ts
5789
+ function useVoiceWorkflowStatus(path = "/evals/scenarios/json", options = {}) {
5790
+ const store = createVoiceWorkflowStatusStore(path, options);
5791
+ const error = ref14(null);
5792
+ const isLoading = ref14(false);
5793
+ const report = shallowRef18(undefined);
5794
+ const updatedAt = ref14(undefined);
1363
5795
  const sync = () => {
1364
5796
  const snapshot = store.getSnapshot();
1365
5797
  error.value = snapshot.error;
1366
5798
  isLoading.value = snapshot.isLoading;
1367
- providers.value = [...snapshot.providers];
5799
+ report.value = snapshot.report;
1368
5800
  updatedAt.value = snapshot.updatedAt;
1369
5801
  };
1370
5802
  const unsubscribe = store.subscribe(sync);
1371
5803
  sync();
1372
- store.refresh().catch(() => {});
1373
- onUnmounted3(() => {
5804
+ if (typeof window !== "undefined") {
5805
+ store.refresh().catch(() => {});
5806
+ }
5807
+ onUnmounted19(() => {
1374
5808
  unsubscribe();
1375
5809
  store.close();
1376
5810
  });
1377
5811
  return {
1378
5812
  error,
1379
5813
  isLoading,
1380
- providers,
1381
5814
  refresh: store.refresh,
5815
+ report,
1382
5816
  updatedAt
1383
5817
  };
1384
- };
5818
+ }
1385
5819
  export {
5820
+ useVoiceWorkflowStatus,
5821
+ useVoiceTurnQuality,
5822
+ useVoiceTurnLatency,
5823
+ useVoiceTraceTimeline,
1386
5824
  useVoiceStream,
5825
+ useVoiceRoutingStatus,
1387
5826
  useVoiceProviderStatus,
1388
- useVoiceController
5827
+ useVoiceProviderSimulationControls,
5828
+ useVoiceProviderContracts,
5829
+ useVoiceProviderCapabilities,
5830
+ useVoiceProofTrends,
5831
+ useVoicePlatformCoverage,
5832
+ useVoiceOpsStatus,
5833
+ useVoiceOpsActionCenter,
5834
+ useVoiceLiveOps,
5835
+ useVoiceDeliveryRuntime,
5836
+ useVoiceController,
5837
+ useVoiceCampaignDialerProof,
5838
+ useVoiceAgentSquadStatus,
5839
+ VoiceTurnQuality,
5840
+ VoiceTurnLatency,
5841
+ VoiceRoutingStatus,
5842
+ VoiceProviderStatus,
5843
+ VoiceProviderSimulationControls,
5844
+ VoiceProviderContracts,
5845
+ VoiceProviderCapabilities,
5846
+ VoiceProofTrends,
5847
+ VoicePlatformCoverage,
5848
+ VoiceOpsStatus,
5849
+ VoiceOpsActionCenter,
5850
+ VoiceDeliveryRuntime
1389
5851
  };