@absolutejs/voice 0.0.22-beta.189 → 0.0.22-beta.190

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.
@@ -2252,6 +2252,91 @@ var createVoiceOpsActionCenterStore = (options = {}) => {
2252
2252
  }
2253
2253
  };
2254
2254
  };
2255
+ // src/client/liveOps.ts
2256
+ var postVoiceLiveOpsAction = async (input, options = {}) => {
2257
+ if (!input.sessionId) {
2258
+ throw new Error("Start a voice session before running live ops actions.");
2259
+ }
2260
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2261
+ const response = await fetchImpl(options.actionPath ?? "/api/voice/live-ops/action", {
2262
+ body: JSON.stringify(input),
2263
+ headers: {
2264
+ "Content-Type": "application/json"
2265
+ },
2266
+ method: "POST"
2267
+ });
2268
+ const payload = await response.json().catch(() => null);
2269
+ if (!response.ok || !payload?.ok) {
2270
+ const message = payload && typeof payload === "object" && "error" in payload ? String(payload.error) : `Voice live ops action failed: HTTP ${response.status}`;
2271
+ throw new Error(message);
2272
+ }
2273
+ return payload;
2274
+ };
2275
+ var createVoiceLiveOpsStore = (options = {}) => {
2276
+ const listeners = new Set;
2277
+ let closed = false;
2278
+ let snapshot = {
2279
+ error: null,
2280
+ isRunning: false
2281
+ };
2282
+ const emit = () => {
2283
+ for (const listener of listeners) {
2284
+ listener();
2285
+ }
2286
+ };
2287
+ const run = async (input) => {
2288
+ if (closed) {
2289
+ return snapshot.lastResult;
2290
+ }
2291
+ snapshot = {
2292
+ ...snapshot,
2293
+ error: null,
2294
+ isRunning: true,
2295
+ runningAction: input.action
2296
+ };
2297
+ emit();
2298
+ try {
2299
+ const result = await postVoiceLiveOpsAction(input, options);
2300
+ await options.onControl?.(result);
2301
+ snapshot = {
2302
+ ...snapshot,
2303
+ error: null,
2304
+ isRunning: false,
2305
+ lastResult: result,
2306
+ runningAction: undefined,
2307
+ updatedAt: Date.now()
2308
+ };
2309
+ emit();
2310
+ return result;
2311
+ } catch (error) {
2312
+ snapshot = {
2313
+ ...snapshot,
2314
+ error: error instanceof Error ? error.message : String(error),
2315
+ isRunning: false,
2316
+ runningAction: undefined,
2317
+ updatedAt: Date.now()
2318
+ };
2319
+ emit();
2320
+ throw error;
2321
+ }
2322
+ };
2323
+ const close = () => {
2324
+ closed = true;
2325
+ listeners.clear();
2326
+ };
2327
+ return {
2328
+ close,
2329
+ getServerSnapshot: () => snapshot,
2330
+ getSnapshot: () => snapshot,
2331
+ run,
2332
+ subscribe: (listener) => {
2333
+ listeners.add(listener);
2334
+ return () => {
2335
+ listeners.delete(listener);
2336
+ };
2337
+ }
2338
+ };
2339
+ };
2255
2340
  // src/client/opsActionHistory.ts
2256
2341
  var fetchVoiceOpsActionHistory = async (path = "/api/voice/ops-actions/history", options = {}) => {
2257
2342
  const fetchImpl = options.fetch ?? globalThis.fetch;
@@ -2673,17 +2758,1205 @@ var defineVoiceOpsActionCenterElement = (tagName = "absolute-voice-ops-action-ce
2673
2758
  }
2674
2759
  });
2675
2760
  };
2761
+ // src/liveOps.ts
2762
+ import { Elysia } from "elysia";
2763
+
2764
+ // src/audit.ts
2765
+ var includes = (filter, value) => {
2766
+ if (!filter) {
2767
+ return true;
2768
+ }
2769
+ if (!value) {
2770
+ return false;
2771
+ }
2772
+ return Array.isArray(filter) ? filter.includes(value) : filter === value;
2773
+ };
2774
+ var createVoiceAuditEvent = (event) => ({
2775
+ ...event,
2776
+ at: event.at ?? Date.now(),
2777
+ id: event.id ?? crypto.randomUUID()
2778
+ });
2779
+ var filterVoiceAuditEvents = (events, filter = {}) => {
2780
+ const sorted = events.filter((event) => {
2781
+ if (!includes(filter.type, event.type)) {
2782
+ return false;
2783
+ }
2784
+ if (!includes(filter.outcome, event.outcome)) {
2785
+ return false;
2786
+ }
2787
+ if (filter.actorId && event.actor?.id !== filter.actorId) {
2788
+ return false;
2789
+ }
2790
+ if (filter.resourceId && event.resource?.id !== filter.resourceId) {
2791
+ return false;
2792
+ }
2793
+ if (filter.resourceType && event.resource?.type !== filter.resourceType) {
2794
+ return false;
2795
+ }
2796
+ if (filter.sessionId && event.sessionId !== filter.sessionId) {
2797
+ return false;
2798
+ }
2799
+ if (filter.traceId && event.traceId !== filter.traceId) {
2800
+ return false;
2801
+ }
2802
+ if (typeof filter.after === "number" && event.at <= filter.after) {
2803
+ return false;
2804
+ }
2805
+ if (typeof filter.afterOrAt === "number" && event.at < filter.afterOrAt) {
2806
+ return false;
2807
+ }
2808
+ if (typeof filter.before === "number" && event.at >= filter.before) {
2809
+ return false;
2810
+ }
2811
+ if (typeof filter.beforeOrAt === "number" && event.at > filter.beforeOrAt) {
2812
+ return false;
2813
+ }
2814
+ return true;
2815
+ }).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
2816
+ return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
2817
+ };
2818
+ var createVoiceMemoryAuditEventStore = () => {
2819
+ const events = new Map;
2820
+ return {
2821
+ append: (event) => {
2822
+ const stored = createVoiceAuditEvent(event);
2823
+ events.set(stored.id, stored);
2824
+ return stored;
2825
+ },
2826
+ get: (id) => events.get(id),
2827
+ list: (filter) => filterVoiceAuditEvents([...events.values()], filter)
2828
+ };
2829
+ };
2830
+ var recordVoiceAuditEvent = (store, event) => store.append(createVoiceAuditEvent(event));
2831
+ var recordVoiceProviderAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
2832
+ action: `${input.kind}.provider.call`,
2833
+ actor: input.actor,
2834
+ metadata: input.metadata,
2835
+ outcome: input.outcome,
2836
+ payload: {
2837
+ cost: input.cost,
2838
+ elapsedMs: input.elapsedMs,
2839
+ error: input.error,
2840
+ kind: input.kind,
2841
+ model: input.model,
2842
+ provider: input.provider
2843
+ },
2844
+ resource: {
2845
+ id: input.provider,
2846
+ type: "provider"
2847
+ },
2848
+ sessionId: input.sessionId,
2849
+ traceId: input.traceId,
2850
+ type: "provider.call"
2851
+ });
2852
+ var recordVoiceToolAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
2853
+ action: "tool.call",
2854
+ actor: input.actor,
2855
+ metadata: input.metadata,
2856
+ outcome: input.outcome,
2857
+ payload: {
2858
+ elapsedMs: input.elapsedMs,
2859
+ error: input.error,
2860
+ toolCallId: input.toolCallId,
2861
+ toolName: input.toolName
2862
+ },
2863
+ resource: {
2864
+ id: input.toolName,
2865
+ type: "tool"
2866
+ },
2867
+ sessionId: input.sessionId,
2868
+ traceId: input.traceId,
2869
+ type: "tool.call"
2870
+ });
2871
+ var recordVoiceHandoffAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
2872
+ action: "handoff",
2873
+ actor: input.actor,
2874
+ metadata: input.metadata,
2875
+ outcome: input.outcome,
2876
+ payload: {
2877
+ fromAgentId: input.fromAgentId,
2878
+ reason: input.reason,
2879
+ target: input.target,
2880
+ toAgentId: input.toAgentId
2881
+ },
2882
+ resource: {
2883
+ id: input.toAgentId ?? input.target,
2884
+ type: "handoff"
2885
+ },
2886
+ sessionId: input.sessionId,
2887
+ traceId: input.traceId,
2888
+ type: "handoff"
2889
+ });
2890
+ var recordVoiceRetentionAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
2891
+ action: input.dryRun ? "retention.plan" : "retention.apply",
2892
+ actor: input.actor ?? {
2893
+ id: "voice-retention",
2894
+ kind: "system"
2895
+ },
2896
+ metadata: input.metadata,
2897
+ outcome: "success",
2898
+ payload: {
2899
+ deletedCount: input.report.deletedCount,
2900
+ dryRun: input.dryRun,
2901
+ scopes: input.report.scopes
2902
+ },
2903
+ resource: {
2904
+ type: "retention-policy"
2905
+ },
2906
+ type: "retention.policy"
2907
+ });
2908
+ var recordVoiceOperatorAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
2909
+ action: input.action,
2910
+ actor: input.actor,
2911
+ metadata: input.metadata,
2912
+ outcome: input.outcome ?? "success",
2913
+ payload: input.payload,
2914
+ resource: input.resource,
2915
+ sessionId: input.sessionId,
2916
+ traceId: input.traceId,
2917
+ type: "operator.action"
2918
+ });
2919
+ var createVoiceAuditLogger = (store) => ({
2920
+ handoff: (input) => recordVoiceHandoffAuditEvent({ ...input, store }),
2921
+ operatorAction: (input) => recordVoiceOperatorAuditEvent({ ...input, store }),
2922
+ providerCall: (input) => recordVoiceProviderAuditEvent({ ...input, store }),
2923
+ record: (event) => recordVoiceAuditEvent(store, event),
2924
+ retention: (input) => recordVoiceRetentionAuditEvent({ ...input, store }),
2925
+ toolCall: (input) => recordVoiceToolAuditEvent({ ...input, store })
2926
+ });
2927
+
2928
+ // src/trace.ts
2929
+ var createVoiceTraceEventId = (event) => [
2930
+ event.sessionId,
2931
+ event.turnId ?? "session",
2932
+ event.type,
2933
+ String(event.at ?? Date.now()),
2934
+ crypto.randomUUID()
2935
+ ].map(encodeURIComponent).join(":");
2936
+ var createVoiceTraceEvent = (event) => ({
2937
+ ...event,
2938
+ at: event.at,
2939
+ id: event.id ?? createVoiceTraceEventId({
2940
+ at: event.at,
2941
+ sessionId: event.sessionId,
2942
+ turnId: event.turnId,
2943
+ type: event.type
2944
+ })
2945
+ });
2946
+ var createVoiceTraceSinkDeliveryId = (events) => {
2947
+ const firstEvent = events[0];
2948
+ return [
2949
+ firstEvent?.sessionId ?? "trace",
2950
+ firstEvent?.traceId ?? "sink",
2951
+ String(firstEvent?.at ?? Date.now()),
2952
+ crypto.randomUUID()
2953
+ ].map(encodeURIComponent).join(":");
2954
+ };
2955
+ var createVoiceTraceSinkDeliveryRecord = (input) => {
2956
+ const createdAt = input.createdAt ?? Date.now();
2957
+ return {
2958
+ createdAt,
2959
+ deliveredAt: input.deliveredAt,
2960
+ deliveryAttempts: input.deliveryAttempts,
2961
+ deliveryError: input.deliveryError,
2962
+ deliveryStatus: input.deliveryStatus ?? "pending",
2963
+ events: input.events,
2964
+ id: input.id ?? createVoiceTraceSinkDeliveryId(input.events),
2965
+ sinkDeliveries: input.sinkDeliveries,
2966
+ updatedAt: input.updatedAt ?? createdAt
2967
+ };
2968
+ };
2969
+ var matchesTraceFilter = (event, filter) => {
2970
+ if (filter.sessionId !== undefined && event.sessionId !== filter.sessionId) {
2971
+ return false;
2972
+ }
2973
+ if (filter.turnId !== undefined && event.turnId !== filter.turnId) {
2974
+ return false;
2975
+ }
2976
+ if (filter.scenarioId !== undefined && event.scenarioId !== filter.scenarioId) {
2977
+ return false;
2978
+ }
2979
+ if (filter.traceId !== undefined && event.traceId !== filter.traceId) {
2980
+ return false;
2981
+ }
2982
+ if (filter.type !== undefined) {
2983
+ const types = Array.isArray(filter.type) ? filter.type : [filter.type];
2984
+ if (!types.includes(event.type)) {
2985
+ return false;
2986
+ }
2987
+ }
2988
+ return true;
2989
+ };
2990
+ var filterVoiceTraceEvents = (events, filter = {}) => {
2991
+ const sorted = events.filter((event) => matchesTraceFilter(event, filter)).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
2992
+ return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
2993
+ };
2994
+ var isPruneTimeMatch = (event, options) => {
2995
+ if (typeof options.before === "number" && event.at >= options.before) {
2996
+ return false;
2997
+ }
2998
+ if (typeof options.beforeOrAt === "number" && event.at > options.beforeOrAt) {
2999
+ return false;
3000
+ }
3001
+ return true;
3002
+ };
3003
+ var selectVoiceTraceEventsForPrune = (events, options = {}) => {
3004
+ let candidates = filterVoiceTraceEvents(events, options.filter).filter((event) => isPruneTimeMatch(event, options));
3005
+ if (typeof options.keepNewest === "number" && options.keepNewest >= 0) {
3006
+ const newestIds = new Set([...candidates].sort((left, right) => right.at - left.at || right.id.localeCompare(left.id)).slice(0, options.keepNewest).map((event) => event.id));
3007
+ candidates = candidates.filter((event) => !newestIds.has(event.id));
3008
+ }
3009
+ return typeof options.limit === "number" && options.limit >= 0 ? candidates.slice(0, options.limit) : candidates;
3010
+ };
3011
+ var pruneVoiceTraceEvents = async (options) => {
3012
+ const events = await options.store.list(options.filter);
3013
+ const deleted = selectVoiceTraceEventsForPrune(events, options);
3014
+ if (!options.dryRun) {
3015
+ await Promise.all(deleted.map((event) => options.store.remove(event.id)));
3016
+ }
3017
+ return {
3018
+ deleted,
3019
+ deletedCount: deleted.length,
3020
+ dryRun: Boolean(options.dryRun),
3021
+ scannedCount: events.length
3022
+ };
3023
+ };
3024
+ var sleep = async (delayMs) => {
3025
+ if (delayMs <= 0) {
3026
+ return;
3027
+ }
3028
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
3029
+ };
3030
+ var toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
3031
+ var signVoiceTraceSinkBody = async (input) => {
3032
+ const encoder = new TextEncoder;
3033
+ const key = await crypto.subtle.importKey("raw", encoder.encode(input.secret), {
3034
+ hash: "SHA-256",
3035
+ name: "HMAC"
3036
+ }, false, ["sign"]);
3037
+ const payload = encoder.encode(`${input.timestamp}.${input.body}`);
3038
+ const signature = await crypto.subtle.sign("HMAC", key, payload);
3039
+ return `sha256=${toHex(new Uint8Array(signature))}`;
3040
+ };
3041
+ var createVoiceTraceSinkDeliveryError = (input) => {
3042
+ if (input.response) {
3043
+ const statusText = input.response.statusText?.trim();
3044
+ return `Attempt ${input.attempt} failed with trace sink response ${input.response.status}${statusText ? ` ${statusText}` : ""}.`;
3045
+ }
3046
+ if (input.error instanceof Error) {
3047
+ return `Attempt ${input.attempt} failed: ${input.error.message}`;
3048
+ }
3049
+ return `Attempt ${input.attempt} failed: ${String(input.error)}`;
3050
+ };
3051
+ var normalizeVoiceTraceS3KeyPrefix = (prefix) => prefix?.trim().replace(/^\/+|\/+$/g, "") ?? "voice/trace-deliveries";
3052
+ var createVoiceTraceS3ObjectKey = (prefix, events) => {
3053
+ const firstEvent = events[0];
3054
+ const safeSessionId = encodeURIComponent(firstEvent?.sessionId ?? "trace");
3055
+ const safeEventId = encodeURIComponent(firstEvent?.id ?? crypto.randomUUID());
3056
+ return `${prefix}/${safeSessionId}/${Date.now()}-${safeEventId}.json`;
3057
+ };
3058
+ var resolveVoiceS3DeliveredTo = (options, key) => {
3059
+ const bucket = options.bucket;
3060
+ return bucket ? `s3://${bucket}/${key}` : `s3://${key}`;
3061
+ };
3062
+ var aggregateVoiceTraceSinkDeliveryStatus = (deliveries) => {
3063
+ const statuses = Object.values(deliveries).map((delivery) => delivery.status);
3064
+ if (statuses.length === 0 || statuses.every((status) => status === "skipped")) {
3065
+ return "skipped";
3066
+ }
3067
+ if (statuses.some((status) => status === "failed")) {
3068
+ return "failed";
3069
+ }
3070
+ return "delivered";
3071
+ };
3072
+ var createVoiceTraceHTTPSink = (options) => ({
3073
+ deliver: async ({ events }) => {
3074
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3075
+ if (typeof fetchImpl !== "function") {
3076
+ return {
3077
+ attempts: 0,
3078
+ deliveredTo: options.url,
3079
+ error: "Trace sink delivery failed: fetch is not available in this runtime.",
3080
+ eventCount: events.length,
3081
+ status: "failed"
3082
+ };
3083
+ }
3084
+ const maxRetries = Math.max(0, options.retries ?? 0);
3085
+ const backoffMs = Math.max(0, options.backoffMs ?? 250);
3086
+ const timeoutMs = Math.max(0, options.timeoutMs ?? 1e4);
3087
+ const payload = options.body ? await options.body({ events }) : {
3088
+ eventCount: events.length,
3089
+ events,
3090
+ source: "absolutejs-voice"
3091
+ };
3092
+ const body = JSON.stringify(payload);
3093
+ let lastError = "Trace sink delivery failed.";
3094
+ for (let attempt = 1;attempt <= maxRetries + 1; attempt += 1) {
3095
+ let controller;
3096
+ let timeout;
3097
+ try {
3098
+ const headers = {
3099
+ "content-type": "application/json",
3100
+ ...options.headers
3101
+ };
3102
+ if (options.signingSecret) {
3103
+ const timestamp = String(Date.now());
3104
+ headers["x-absolutejs-timestamp"] = timestamp;
3105
+ headers["x-absolutejs-signature"] = await signVoiceTraceSinkBody({
3106
+ body,
3107
+ secret: options.signingSecret,
3108
+ timestamp
3109
+ });
3110
+ }
3111
+ controller = timeoutMs > 0 ? new AbortController : undefined;
3112
+ if (controller && timeoutMs > 0) {
3113
+ timeout = setTimeout(() => controller?.abort(), timeoutMs);
3114
+ }
3115
+ const response = await fetchImpl(options.url, {
3116
+ body,
3117
+ headers,
3118
+ method: options.method ?? "POST",
3119
+ signal: controller?.signal
3120
+ });
3121
+ if (response.ok) {
3122
+ let responseBody;
3123
+ try {
3124
+ responseBody = await response.clone().json();
3125
+ } catch {
3126
+ responseBody = undefined;
3127
+ }
3128
+ return {
3129
+ attempts: attempt,
3130
+ deliveredAt: Date.now(),
3131
+ deliveredTo: options.url,
3132
+ eventCount: events.length,
3133
+ responseBody,
3134
+ status: "delivered"
3135
+ };
3136
+ }
3137
+ lastError = createVoiceTraceSinkDeliveryError({
3138
+ attempt,
3139
+ response
3140
+ });
3141
+ } catch (error) {
3142
+ lastError = createVoiceTraceSinkDeliveryError({
3143
+ attempt,
3144
+ error
3145
+ });
3146
+ } finally {
3147
+ if (timeout) {
3148
+ clearTimeout(timeout);
3149
+ }
3150
+ }
3151
+ if (attempt <= maxRetries) {
3152
+ await sleep(backoffMs * attempt);
3153
+ }
3154
+ }
3155
+ return {
3156
+ attempts: maxRetries + 1,
3157
+ deliveredTo: options.url,
3158
+ error: lastError,
3159
+ eventCount: events.length,
3160
+ status: "failed"
3161
+ };
3162
+ },
3163
+ eventTypes: options.eventTypes,
3164
+ id: options.id,
3165
+ kind: options.kind ?? "http"
3166
+ });
3167
+ var createVoiceTraceS3Sink = (options) => {
3168
+ const client = options.client ?? new Bun.S3Client(options);
3169
+ const keyPrefix = normalizeVoiceTraceS3KeyPrefix(options.keyPrefix);
3170
+ return {
3171
+ deliver: async ({ events }) => {
3172
+ const key = createVoiceTraceS3ObjectKey(keyPrefix, events);
3173
+ const payload = options.body ? await options.body({ events, key }) : {
3174
+ eventCount: events.length,
3175
+ events,
3176
+ key,
3177
+ source: "absolutejs-voice"
3178
+ };
3179
+ try {
3180
+ const file = client.file(key, options);
3181
+ await file.write(JSON.stringify(payload), {
3182
+ type: options.contentType ?? "application/json"
3183
+ });
3184
+ return {
3185
+ attempts: 1,
3186
+ deliveredAt: Date.now(),
3187
+ deliveredTo: resolveVoiceS3DeliveredTo(options, key),
3188
+ eventCount: events.length,
3189
+ responseBody: { key },
3190
+ status: "delivered"
3191
+ };
3192
+ } catch (error) {
3193
+ return {
3194
+ attempts: 1,
3195
+ deliveredTo: resolveVoiceS3DeliveredTo(options, key),
3196
+ error: error instanceof Error ? error.message : String(error),
3197
+ eventCount: events.length,
3198
+ status: "failed"
3199
+ };
3200
+ }
3201
+ },
3202
+ eventTypes: options.eventTypes,
3203
+ id: options.id,
3204
+ kind: options.kind ?? "s3"
3205
+ };
3206
+ };
3207
+ var deliverVoiceTraceEventsToSinks = async (input) => {
3208
+ const events = input.redact ? redactVoiceTraceEvents(input.events, input.redact) : input.events;
3209
+ const sinkDeliveries = {};
3210
+ for (const sink of input.sinks) {
3211
+ const sinkEvents = sink.eventTypes?.length ? events.filter((event) => sink.eventTypes?.includes(event.type)) : events;
3212
+ if (sinkEvents.length === 0) {
3213
+ sinkDeliveries[sink.id] = {
3214
+ attempts: 0,
3215
+ eventCount: 0,
3216
+ status: "skipped"
3217
+ };
3218
+ continue;
3219
+ }
3220
+ try {
3221
+ sinkDeliveries[sink.id] = await sink.deliver({
3222
+ events: sinkEvents
3223
+ });
3224
+ } catch (error) {
3225
+ sinkDeliveries[sink.id] = {
3226
+ attempts: 1,
3227
+ error: error instanceof Error ? error.message : String(error),
3228
+ eventCount: sinkEvents.length,
3229
+ status: "failed"
3230
+ };
3231
+ }
3232
+ }
3233
+ return {
3234
+ deliveredAt: Date.now(),
3235
+ eventCount: events.length,
3236
+ sinkDeliveries,
3237
+ status: aggregateVoiceTraceSinkDeliveryStatus(sinkDeliveries)
3238
+ };
3239
+ };
3240
+ var createVoiceTraceSinkStore = (options) => {
3241
+ const deliver = async (event) => {
3242
+ const result = await deliverVoiceTraceEventsToSinks({
3243
+ events: [event],
3244
+ redact: options.redact,
3245
+ sinks: options.sinks
3246
+ });
3247
+ await options.onDelivery?.(result);
3248
+ };
3249
+ return {
3250
+ append: async (event) => {
3251
+ const stored = await options.store.append(event);
3252
+ if (options.deliveryQueue) {
3253
+ const delivery2 = createVoiceTraceSinkDeliveryRecord({
3254
+ events: [stored]
3255
+ });
3256
+ await options.deliveryQueue.set(delivery2.id, delivery2);
3257
+ return stored;
3258
+ }
3259
+ const delivery = deliver(stored);
3260
+ if (options.awaitDelivery) {
3261
+ await delivery;
3262
+ } else {
3263
+ delivery.catch((error) => {
3264
+ options.onError?.(error);
3265
+ });
3266
+ }
3267
+ return stored;
3268
+ },
3269
+ get: (id) => options.store.get(id),
3270
+ list: (filter) => options.store.list(filter),
3271
+ remove: (id) => options.store.remove(id)
3272
+ };
3273
+ };
3274
+ var createVoiceMemoryTraceSinkDeliveryStore = () => {
3275
+ const deliveries = new Map;
3276
+ return {
3277
+ get: async (id) => deliveries.get(id),
3278
+ list: async () => [...deliveries.values()].sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id)),
3279
+ remove: async (id) => {
3280
+ deliveries.delete(id);
3281
+ },
3282
+ set: async (id, delivery) => {
3283
+ deliveries.set(id, delivery);
3284
+ }
3285
+ };
3286
+ };
3287
+ var createVoiceMemoryTraceEventStore = () => {
3288
+ const events = new Map;
3289
+ const append = async (event) => {
3290
+ const stored = createVoiceTraceEvent(event);
3291
+ events.set(stored.id, stored);
3292
+ return stored;
3293
+ };
3294
+ const get = async (id) => events.get(id);
3295
+ const list = async (filter) => filterVoiceTraceEvents([...events.values()], filter);
3296
+ const remove = async (id) => {
3297
+ events.delete(id);
3298
+ };
3299
+ return { append, get, list, remove };
3300
+ };
3301
+ var exportVoiceTrace = async (input) => {
3302
+ const events = await input.store.list(input.filter);
3303
+ return {
3304
+ exportedAt: Date.now(),
3305
+ events: input.redact ? redactVoiceTraceEvents(events, input.redact) : events,
3306
+ filter: input.filter,
3307
+ redacted: Boolean(input.redact)
3308
+ };
3309
+ };
3310
+ var toNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
3311
+ var escapeHtml3 = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
3312
+ var formatTraceValue = (value) => {
3313
+ if (value === undefined || value === null) {
3314
+ return "";
3315
+ }
3316
+ if (typeof value === "string") {
3317
+ return value;
3318
+ }
3319
+ if (typeof value === "number" || typeof value === "boolean") {
3320
+ return String(value);
3321
+ }
3322
+ try {
3323
+ return JSON.stringify(value);
3324
+ } catch {
3325
+ return String(value);
3326
+ }
3327
+ };
3328
+ var DEFAULT_REDACTION_KEYS = [
3329
+ "apiKey",
3330
+ "authorization",
3331
+ "creditCard",
3332
+ "email",
3333
+ "externalId",
3334
+ "password",
3335
+ "phone",
3336
+ "secret",
3337
+ "ssn",
3338
+ "token"
3339
+ ];
3340
+ var DEFAULT_REDACTION_TEXT_KEYS = [
3341
+ "assistantText",
3342
+ "content",
3343
+ "error",
3344
+ "reason",
3345
+ "summary",
3346
+ "text"
3347
+ ];
3348
+ var EMAIL_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
3349
+ var PHONE_PATTERN = /(?<!\d)(?:\+?1[\s.-]?)?(?:\(?\d{3}\)?[\s.-]?)\d{3}[\s.-]?\d{4}(?!\d)/g;
3350
+ var normalizeRedactionKey = (key) => key.trim().toLowerCase().replace(/[^a-z0-9]/g, "");
3351
+ var resolveVoiceTraceRedactionOptions = (options = {}) => ({
3352
+ keys: typeof options === "boolean" ? DEFAULT_REDACTION_KEYS : options.keys ?? DEFAULT_REDACTION_KEYS,
3353
+ redactEmails: typeof options === "boolean" ? true : options.redactEmails ?? true,
3354
+ redactPhoneNumbers: typeof options === "boolean" ? true : options.redactPhoneNumbers ?? true,
3355
+ redactText: typeof options === "boolean" ? true : options.redactText ?? true,
3356
+ replacement: typeof options === "boolean" ? "[redacted]" : options.replacement ?? "[redacted]",
3357
+ textKeys: typeof options === "boolean" ? DEFAULT_REDACTION_TEXT_KEYS : options.textKeys ?? DEFAULT_REDACTION_TEXT_KEYS
3358
+ });
3359
+ var resolveReplacement = (input) => typeof input.options.replacement === "function" ? input.options.replacement({
3360
+ key: input.key,
3361
+ path: input.path,
3362
+ value: input.value
3363
+ }) : input.options.replacement;
3364
+ var redactVoiceTraceText = (value, options = {}, input = {}) => {
3365
+ const resolved = resolveVoiceTraceRedactionOptions(options);
3366
+ let redacted = value;
3367
+ const replacement = resolveReplacement({
3368
+ key: input.key,
3369
+ options: resolved,
3370
+ path: input.path ?? [],
3371
+ value
3372
+ });
3373
+ if (resolved.redactEmails) {
3374
+ redacted = redacted.replace(EMAIL_PATTERN, replacement);
3375
+ }
3376
+ if (resolved.redactPhoneNumbers) {
3377
+ redacted = redacted.replace(PHONE_PATTERN, replacement);
3378
+ }
3379
+ return redacted;
3380
+ };
3381
+ var redactTraceValue = (value, options, path) => {
3382
+ const key = path.at(-1);
3383
+ const normalizedKey = key ? normalizeRedactionKey(key) : undefined;
3384
+ const sensitiveKeys = new Set(options.keys.map(normalizeRedactionKey));
3385
+ const textKeys = new Set(options.textKeys.map(normalizeRedactionKey));
3386
+ if (normalizedKey && sensitiveKeys.has(normalizedKey) && (value === null || ["boolean", "number", "string", "undefined"].includes(typeof value))) {
3387
+ return resolveReplacement({
3388
+ key,
3389
+ options,
3390
+ path,
3391
+ value: String(value ?? "")
3392
+ });
3393
+ }
3394
+ if (typeof value === "string") {
3395
+ const shouldRedactText = options.redactText && (!normalizedKey || textKeys.has(normalizedKey) || path.length === 0);
3396
+ return shouldRedactText ? redactVoiceTraceText(value, options, {
3397
+ key,
3398
+ path
3399
+ }) : value;
3400
+ }
3401
+ if (Array.isArray(value)) {
3402
+ return value.map((item, index) => redactTraceValue(item, options, [...path, String(index)]));
3403
+ }
3404
+ if (typeof value === "object" && value) {
3405
+ return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [
3406
+ entryKey,
3407
+ redactTraceValue(entryValue, options, [...path, entryKey])
3408
+ ]));
3409
+ }
3410
+ return value;
3411
+ };
3412
+ var redactVoiceTraceEvent = (event, options = {}) => {
3413
+ const resolved = resolveVoiceTraceRedactionOptions(options);
3414
+ return {
3415
+ ...event,
3416
+ metadata: redactTraceValue(event.metadata, resolved, ["metadata"]),
3417
+ payload: redactTraceValue(event.payload, resolved, ["payload"])
3418
+ };
3419
+ };
3420
+ var redactVoiceTraceEvents = (events, options = {}) => events.map((event) => redactVoiceTraceEvent(event, options));
3421
+ var summarizeVoiceTrace = (events) => {
3422
+ const sorted = filterVoiceTraceEvents(events);
3423
+ const firstEvent = sorted[0];
3424
+ const lastEvent = sorted.at(-1);
3425
+ const lifecycleEvents = sorted.filter((event) => event.type === "call.lifecycle");
3426
+ const startEvent = lifecycleEvents.find((event) => event.payload.type === "start");
3427
+ const endEvent = lifecycleEvents.toReversed().find((event) => event.payload.type === "end");
3428
+ const costEvents = sorted.filter((event) => event.type === "turn.cost");
3429
+ const toolEvents = sorted.filter((event) => event.type === "agent.tool");
3430
+ const startedAt = startEvent?.at ?? firstEvent?.at;
3431
+ const endedAt = endEvent?.at ?? lastEvent?.at;
3432
+ const failed = sorted.some((event) => event.type === "session.error") || endEvent?.payload.disposition === "failed";
3433
+ return {
3434
+ assistantReplyCount: sorted.filter((event) => event.type === "turn.assistant").length,
3435
+ callDurationMs: startedAt !== undefined && endedAt !== undefined ? Math.max(0, endedAt - startedAt) : undefined,
3436
+ cost: {
3437
+ estimatedRelativeCostUnits: costEvents.reduce((total, event) => total + toNumber(event.payload.estimatedRelativeCostUnits), 0),
3438
+ totalBillableAudioMs: costEvents.reduce((total, event) => total + toNumber(event.payload.totalBillableAudioMs), 0)
3439
+ },
3440
+ endedAt,
3441
+ errorCount: sorted.filter((event) => event.type === "session.error").length,
3442
+ eventCount: sorted.length,
3443
+ failed,
3444
+ handoffCount: sorted.filter((event) => event.type === "agent.handoff").length,
3445
+ modelCallCount: sorted.filter((event) => event.type === "agent.model").length,
3446
+ sessionId: firstEvent?.sessionId,
3447
+ startedAt,
3448
+ toolCallCount: toolEvents.length,
3449
+ toolErrorCount: toolEvents.filter((event) => event.payload.status === "error").length,
3450
+ traceId: firstEvent?.traceId,
3451
+ transcriptCount: sorted.filter((event) => event.type === "turn.transcript").length,
3452
+ turnCount: sorted.filter((event) => event.type === "turn.committed").length
3453
+ };
3454
+ };
3455
+ var evaluateVoiceTrace = (events, options = {}) => {
3456
+ const summary = summarizeVoiceTrace(events);
3457
+ const issues = [];
3458
+ const maxHandoffs = options.maxHandoffs ?? 3;
3459
+ const maxToolErrors = options.maxToolErrors ?? 0;
3460
+ const maxModelCallsPerTurn = options.maxModelCallsPerTurn ?? 6;
3461
+ const turnCountForRatio = Math.max(1, summary.turnCount);
3462
+ if (options.requireCompletedCall !== false && !summary.endedAt) {
3463
+ issues.push({
3464
+ code: "call-not-ended",
3465
+ message: "Trace does not include a call end lifecycle event.",
3466
+ severity: "warning"
3467
+ });
3468
+ }
3469
+ if (summary.failed) {
3470
+ issues.push({
3471
+ code: "session-error",
3472
+ message: "Trace contains a session error or failed call disposition.",
3473
+ severity: "error"
3474
+ });
3475
+ }
3476
+ if (options.requireTranscript !== false && summary.transcriptCount === 0) {
3477
+ issues.push({
3478
+ code: "missing-transcript",
3479
+ message: "Trace does not include any transcript events.",
3480
+ severity: "error"
3481
+ });
3482
+ }
3483
+ if (options.requireTurn !== false && summary.turnCount === 0) {
3484
+ issues.push({
3485
+ code: "missing-turn",
3486
+ message: "Trace does not include any committed turns.",
3487
+ severity: "error"
3488
+ });
3489
+ }
3490
+ if (options.requireAssistantReply !== false && summary.turnCount > 0 && summary.assistantReplyCount === 0) {
3491
+ issues.push({
3492
+ code: "missing-assistant-reply",
3493
+ message: "Trace has committed turns but no assistant replies.",
3494
+ severity: "warning"
3495
+ });
3496
+ }
3497
+ if (summary.toolErrorCount > maxToolErrors) {
3498
+ issues.push({
3499
+ code: "tool-errors",
3500
+ message: `Trace has ${summary.toolErrorCount} tool error(s), above the allowed ${maxToolErrors}.`,
3501
+ severity: "error"
3502
+ });
3503
+ }
3504
+ if (summary.handoffCount > maxHandoffs) {
3505
+ issues.push({
3506
+ code: "too-many-handoffs",
3507
+ message: `Trace has ${summary.handoffCount} handoff(s), above the allowed ${maxHandoffs}.`,
3508
+ severity: "warning"
3509
+ });
3510
+ }
3511
+ if (summary.modelCallCount / turnCountForRatio > maxModelCallsPerTurn) {
3512
+ issues.push({
3513
+ code: "too-many-model-calls",
3514
+ message: `Trace averages more than ${maxModelCallsPerTurn} model calls per committed turn.`,
3515
+ severity: "warning"
3516
+ });
3517
+ }
3518
+ return {
3519
+ issues,
3520
+ pass: !issues.some((issue) => issue.severity === "error"),
3521
+ summary
3522
+ };
3523
+ };
3524
+ var renderTraceEventMarkdown = (event, startedAt) => {
3525
+ const offset = startedAt === undefined ? `${event.at}` : `+${Math.max(0, event.at - startedAt)}ms`;
3526
+ const label = `- ${offset} [${event.type}]`;
3527
+ switch (event.type) {
3528
+ case "turn.transcript":
3529
+ return `${label} ${event.payload.isFinal ? "final" : "partial"} "${formatTraceValue(event.payload.text)}"`;
3530
+ case "turn.committed":
3531
+ return `${label} committed "${formatTraceValue(event.payload.text)}"`;
3532
+ case "turn.assistant":
3533
+ return event.payload.text ? `${label} assistant "${formatTraceValue(event.payload.text)}"` : `${label} ${formatTraceValue(event.payload.status)}`;
3534
+ case "agent.tool":
3535
+ return `${label} ${formatTraceValue(event.payload.toolName)} ${formatTraceValue(event.payload.status)}`;
3536
+ case "agent.handoff":
3537
+ return `${label} ${formatTraceValue(event.payload.fromAgentId)} -> ${formatTraceValue(event.payload.targetAgentId)}`;
3538
+ case "session.error":
3539
+ return `${label} ${formatTraceValue(event.payload.error)}`;
3540
+ case "call.lifecycle":
3541
+ return `${label} ${formatTraceValue(event.payload.type)} ${formatTraceValue(event.payload.disposition)}`.trim();
3542
+ default:
3543
+ return `${label} ${formatTraceValue(event.payload)}`;
3544
+ }
3545
+ };
3546
+ var renderVoiceTraceMarkdown = (events, options = {}) => {
3547
+ const sorted = filterVoiceTraceEvents(options.redact ? redactVoiceTraceEvents(events, options.redact) : events);
3548
+ const summary = summarizeVoiceTrace(sorted);
3549
+ const evaluation = evaluateVoiceTrace(sorted, options.evaluation);
3550
+ const lines = [
3551
+ `# ${options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim()}`,
3552
+ "",
3553
+ `Pass: ${evaluation.pass ? "yes" : "no"}`,
3554
+ `Session: ${summary.sessionId ?? "unknown"}`,
3555
+ `Events: ${summary.eventCount}`,
3556
+ `Turns: ${summary.turnCount}`,
3557
+ `Transcripts: ${summary.transcriptCount}`,
3558
+ `Assistant replies: ${summary.assistantReplyCount}`,
3559
+ `Model calls: ${summary.modelCallCount}`,
3560
+ `Tool calls: ${summary.toolCallCount}`,
3561
+ `Handoffs: ${summary.handoffCount}`,
3562
+ `Errors: ${summary.errorCount}`,
3563
+ `Estimated cost units: ${summary.cost.estimatedRelativeCostUnits}`,
3564
+ ""
3565
+ ];
3566
+ if (evaluation.issues.length > 0) {
3567
+ lines.push("## Issues", "");
3568
+ for (const issue of evaluation.issues) {
3569
+ lines.push(`- [${issue.severity}] ${issue.code}: ${issue.message}`);
3570
+ }
3571
+ lines.push("");
3572
+ }
3573
+ lines.push("## Timeline", "");
3574
+ for (const event of sorted) {
3575
+ lines.push(renderTraceEventMarkdown(event, summary.startedAt));
3576
+ }
3577
+ return lines.join(`
3578
+ `);
3579
+ };
3580
+ var renderVoiceTraceHTML = (events, options = {}) => {
3581
+ const markdown = renderVoiceTraceMarkdown(events, options);
3582
+ const renderEvents = options.redact ? redactVoiceTraceEvents(events, options.redact) : events;
3583
+ const summary = summarizeVoiceTrace(renderEvents);
3584
+ const evaluation = evaluateVoiceTrace(renderEvents, options.evaluation);
3585
+ const eventRows = filterVoiceTraceEvents(renderEvents).map((event) => {
3586
+ const offset = summary.startedAt === undefined ? event.at : Math.max(0, event.at - summary.startedAt);
3587
+ return [
3588
+ "<tr>",
3589
+ `<td>${escapeHtml3(String(offset))}</td>`,
3590
+ `<td>${escapeHtml3(event.type)}</td>`,
3591
+ `<td>${escapeHtml3(event.turnId ?? "")}</td>`,
3592
+ `<td><code>${escapeHtml3(JSON.stringify(event.payload))}</code></td>`,
3593
+ "</tr>"
3594
+ ].join("");
3595
+ }).join(`
3596
+ `);
3597
+ return [
3598
+ "<!doctype html>",
3599
+ '<html lang="en">',
3600
+ "<head>",
3601
+ '<meta charset="utf-8" />',
3602
+ '<meta name="viewport" content="width=device-width, initial-scale=1" />',
3603
+ `<title>${escapeHtml3(options.title ?? "Voice Trace")}</title>`,
3604
+ "<style>",
3605
+ "body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;line-height:1.45;background:#f8f7f2;color:#181713}",
3606
+ "main{max-width:1100px;margin:auto}",
3607
+ ".summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:.75rem;margin:1rem 0}",
3608
+ ".card{background:white;border:1px solid #ded9cc;border-radius:12px;padding:1rem}",
3609
+ ".pass{color:#126b3a}.fail{color:#9d2222}",
3610
+ "table{border-collapse:collapse;width:100%;background:white;border:1px solid #ded9cc}",
3611
+ "th,td{border-bottom:1px solid #eee8dc;padding:.65rem;text-align:left;vertical-align:top}",
3612
+ "code{white-space:pre-wrap;word-break:break-word}",
3613
+ "pre{background:#181713;color:#f8f7f2;padding:1rem;border-radius:12px;overflow:auto}",
3614
+ "</style>",
3615
+ "</head>",
3616
+ "<body><main>",
3617
+ `<h1>${escapeHtml3(options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim())}</h1>`,
3618
+ `<p class="${evaluation.pass ? "pass" : "fail"}">QA: ${evaluation.pass ? "pass" : "fail"}</p>`,
3619
+ '<section class="summary">',
3620
+ `<div class="card"><strong>Events</strong><br>${summary.eventCount}</div>`,
3621
+ `<div class="card"><strong>Turns</strong><br>${summary.turnCount}</div>`,
3622
+ `<div class="card"><strong>Transcripts</strong><br>${summary.transcriptCount}</div>`,
3623
+ `<div class="card"><strong>Tool errors</strong><br>${summary.toolErrorCount}</div>`,
3624
+ `<div class="card"><strong>Cost units</strong><br>${summary.cost.estimatedRelativeCostUnits}</div>`,
3625
+ "</section>",
3626
+ "<h2>Timeline</h2>",
3627
+ "<table><thead><tr><th>Offset ms</th><th>Type</th><th>Turn</th><th>Payload</th></tr></thead><tbody>",
3628
+ eventRows,
3629
+ "</tbody></table>",
3630
+ "<h2>Markdown Export</h2>",
3631
+ `<pre>${escapeHtml3(markdown)}</pre>`,
3632
+ "</main></body></html>"
3633
+ ].join(`
3634
+ `);
3635
+ };
3636
+ var buildVoiceTraceReplay = (events, options = {}) => ({
3637
+ evaluation: evaluateVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events, options.evaluation),
3638
+ html: renderVoiceTraceHTML(events, options),
3639
+ markdown: renderVoiceTraceMarkdown(events, options),
3640
+ summary: summarizeVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events)
3641
+ });
3642
+
3643
+ // src/liveOps.ts
3644
+ var VOICE_LIVE_OPS_ACTIONS = [
3645
+ "assign",
3646
+ "create-task",
3647
+ "escalate",
3648
+ "force-handoff",
3649
+ "inject-instruction",
3650
+ "operator-takeover",
3651
+ "pause-assistant",
3652
+ "resume-assistant",
3653
+ "tag"
3654
+ ];
3655
+ var isVoiceLiveOpsAction = (value) => typeof value === "string" && VOICE_LIVE_OPS_ACTIONS.includes(value);
3656
+ var toStringValue = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
3657
+ var createVoiceMemoryLiveOpsControlStore = () => {
3658
+ const states = new Map;
3659
+ return {
3660
+ get: (sessionId) => states.get(sessionId),
3661
+ set: (sessionId, state) => {
3662
+ states.set(sessionId, state);
3663
+ }
3664
+ };
3665
+ };
3666
+ var getVoiceLiveOpsControlStatus = (action) => {
3667
+ switch (action) {
3668
+ case "force-handoff":
3669
+ return "handoff-forced";
3670
+ case "inject-instruction":
3671
+ return "instruction-injected";
3672
+ case "operator-takeover":
3673
+ return "operator-takeover";
3674
+ case "pause-assistant":
3675
+ return "assistant-paused";
3676
+ case "resume-assistant":
3677
+ return "assistant-resumed";
3678
+ default:
3679
+ return "recorded";
3680
+ }
3681
+ };
3682
+ var buildVoiceLiveOpsControlState = (input) => ({
3683
+ assistantPaused: input.action === "pause-assistant" || input.action === "operator-takeover" || input.action === "force-handoff" ? true : input.action === "resume-assistant" ? false : input.previous?.assistantPaused ?? false,
3684
+ handoffTarget: input.action === "force-handoff" ? input.tag : input.previous?.handoffTarget,
3685
+ injectedInstruction: input.action === "inject-instruction" ? input.detail : input.previous?.injectedInstruction,
3686
+ lastAction: input.action,
3687
+ lastUpdatedAt: input.at ?? Date.now(),
3688
+ operator: input.assignee,
3689
+ operatorTakeover: input.action === "operator-takeover" ? true : input.action === "resume-assistant" ? false : input.previous?.operatorTakeover ?? false,
3690
+ status: getVoiceLiveOpsControlStatus(input.action),
3691
+ tag: input.tag
3692
+ });
3693
+ var createVoiceLiveOpsController = (options = {}) => {
3694
+ const store = options.store ?? createVoiceMemoryLiveOpsControlStore();
3695
+ const perform = async (input) => {
3696
+ if (!input.sessionId) {
3697
+ throw new Error("Voice live ops action requires sessionId.");
3698
+ }
3699
+ if (!isVoiceLiveOpsAction(input.action)) {
3700
+ throw new Error("Voice live ops action is not supported.");
3701
+ }
3702
+ const at = Date.now();
3703
+ const assignee = input.assignee ?? options.defaultAssignee ?? "operator";
3704
+ const tag = input.tag ?? options.defaultTag ?? "live-ops";
3705
+ const detail = input.detail ?? options.defaultDetail ?? input.action;
3706
+ const previous = await store.get(input.sessionId);
3707
+ const control = buildVoiceLiveOpsControlState({
3708
+ ...input,
3709
+ assignee,
3710
+ at,
3711
+ detail,
3712
+ previous,
3713
+ tag
3714
+ });
3715
+ await store.set(input.sessionId, control);
3716
+ const traceId = `voice-live-ops:${input.sessionId}:${input.action}:${at}`;
3717
+ await Promise.all([
3718
+ options.audit?.append(createVoiceAuditEvent({
3719
+ action: `voice.live_ops.${input.action}`,
3720
+ actor: {
3721
+ id: assignee,
3722
+ kind: "operator",
3723
+ name: assignee
3724
+ },
3725
+ at,
3726
+ metadata: {
3727
+ source: "voice-live-ops",
3728
+ tag
3729
+ },
3730
+ outcome: "success",
3731
+ payload: {
3732
+ action: input.action,
3733
+ assignee,
3734
+ control,
3735
+ detail,
3736
+ tag
3737
+ },
3738
+ resource: {
3739
+ id: input.sessionId,
3740
+ type: "voice.session"
3741
+ },
3742
+ sessionId: input.sessionId,
3743
+ traceId,
3744
+ type: "operator.action"
3745
+ })),
3746
+ options.trace?.append(createVoiceTraceEvent({
3747
+ at,
3748
+ metadata: {
3749
+ source: "voice-live-ops",
3750
+ tag
3751
+ },
3752
+ payload: {
3753
+ action: input.action,
3754
+ assignee,
3755
+ control,
3756
+ detail,
3757
+ status: "success",
3758
+ tag
3759
+ },
3760
+ sessionId: input.sessionId,
3761
+ traceId,
3762
+ type: "operator.action"
3763
+ }))
3764
+ ]);
3765
+ const result = {
3766
+ action: input.action,
3767
+ control,
3768
+ ok: true,
3769
+ sessionId: input.sessionId
3770
+ };
3771
+ await options.onAction?.({
3772
+ ...result,
3773
+ assignee,
3774
+ detail,
3775
+ tag
3776
+ });
3777
+ return result;
3778
+ };
3779
+ return {
3780
+ get: (sessionId) => store.get(sessionId),
3781
+ perform,
3782
+ store
3783
+ };
3784
+ };
3785
+ var readVoiceLiveOpsActionInput = async (request) => {
3786
+ const body = await request.json().catch(() => null);
3787
+ if (!body || typeof body !== "object") {
3788
+ throw new Error("Voice live ops action requires a JSON body.");
3789
+ }
3790
+ const record = body;
3791
+ const action = record.action;
3792
+ const sessionId = toStringValue(record.sessionId);
3793
+ if (!sessionId || !isVoiceLiveOpsAction(action)) {
3794
+ throw new Error("Voice live ops action requires valid sessionId and action.");
3795
+ }
3796
+ return {
3797
+ action,
3798
+ assignee: toStringValue(record.assignee),
3799
+ detail: toStringValue(record.detail),
3800
+ sessionId,
3801
+ tag: toStringValue(record.tag)
3802
+ };
3803
+ };
3804
+ var createVoiceLiveOpsRoutes = (options = {}) => {
3805
+ const controller = createVoiceLiveOpsController(options);
3806
+ const path = options.path ?? "/api/voice/live-ops/action";
3807
+ const controlPath = options.controlPath ?? "/api/voice/live-ops/control/:sessionId";
3808
+ return new Elysia({
3809
+ name: options.name ?? "absolutejs-voice-live-ops"
3810
+ }).post(path, async ({ request, set }) => {
3811
+ try {
3812
+ return await controller.perform(await readVoiceLiveOpsActionInput(request));
3813
+ } catch (error) {
3814
+ set.status = 400;
3815
+ return {
3816
+ error: error instanceof Error ? error.message : String(error),
3817
+ ok: false
3818
+ };
3819
+ }
3820
+ }).get(controlPath, async ({ params }) => {
3821
+ const sessionId = params.sessionId;
3822
+ return {
3823
+ control: await controller.get(sessionId),
3824
+ ok: true,
3825
+ sessionId
3826
+ };
3827
+ });
3828
+ };
3829
+
3830
+ // src/client/liveOpsWidget.ts
3831
+ var ACTION_LABELS = {
3832
+ assign: "Assign",
3833
+ "create-task": "Create task",
3834
+ escalate: "Escalate",
3835
+ "force-handoff": "Force handoff",
3836
+ "inject-instruction": "Inject instruction",
3837
+ "operator-takeover": "Take over",
3838
+ "pause-assistant": "Pause assistant",
3839
+ "resume-assistant": "Resume assistant",
3840
+ tag: "Tag"
3841
+ };
3842
+ var escapeHtml4 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3843
+ var createVoiceLiveOpsInput = (action, input) => ({
3844
+ action,
3845
+ assignee: input.assignee,
3846
+ detail: input.detail,
3847
+ sessionId: input.sessionId ?? "",
3848
+ tag: input.tag
3849
+ });
3850
+ var renderVoiceLiveOpsHTML = (snapshot, options = {}) => {
3851
+ const sessionId = options.getSessionId?.() ?? "";
3852
+ const disabled = snapshot.isRunning || !sessionId;
3853
+ const actions = VOICE_LIVE_OPS_ACTIONS.map((action) => `<button type="button" data-absolute-voice-live-ops-action="${escapeHtml4(action)}"${disabled ? " disabled" : ""}>${escapeHtml4(snapshot.runningAction === action ? "Running..." : ACTION_LABELS[action])}</button>`).join("");
3854
+ const result = snapshot.error ? `<p class="absolute-voice-live-ops__error">${escapeHtml4(snapshot.error)}</p>` : snapshot.lastResult ? `<p class="absolute-voice-live-ops__result">Recorded ${escapeHtml4(snapshot.lastResult.action)}. Control: ${escapeHtml4(snapshot.lastResult.control.status)}.</p>` : '<p class="absolute-voice-live-ops__result">No live ops action has run yet.</p>';
3855
+ return `<section class="absolute-voice-live-ops">
3856
+ <header class="absolute-voice-live-ops__header">
3857
+ <span>${escapeHtml4(options.title ?? "Live Ops")}</span>
3858
+ <strong>${escapeHtml4(sessionId || "No active session")}</strong>
3859
+ </header>
3860
+ <p class="absolute-voice-live-ops__description">${escapeHtml4(options.description ?? "Pause, resume, take over, force handoff, or inject operator instructions during a live voice session.")}</p>
3861
+ <label><span>Operator</span><input data-absolute-voice-live-ops-assignee value="${escapeHtml4(options.defaultAssignee ?? "operator")}" /></label>
3862
+ <label><span>Tag / handoff target</span><input data-absolute-voice-live-ops-tag value="${escapeHtml4(options.defaultTag ?? "live-ops")}" /></label>
3863
+ <label><span>Detail / instruction</span><input data-absolute-voice-live-ops-detail value="${escapeHtml4(options.defaultDetail ?? "Operator marked this live session.")}" /></label>
3864
+ <div class="absolute-voice-live-ops__actions">${actions}</div>
3865
+ ${result}
3866
+ </section>`;
3867
+ };
3868
+ var getVoiceLiveOpsCSS = () => `.absolute-voice-live-ops{border:1px solid #f59e0b66;border-radius:20px;background:#111827;color:#f8fafc;padding:18px;font-family:inherit}.absolute-voice-live-ops__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-live-ops__header span{color:#fbbf24;font-size:12px;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-live-ops__header strong{font-size:18px;overflow-wrap:anywhere}.absolute-voice-live-ops__description,.absolute-voice-live-ops__result{color:#cbd5e1}.absolute-voice-live-ops label{display:grid;gap:6px;margin-top:12px}.absolute-voice-live-ops label span{color:#94a3b8;font-size:13px}.absolute-voice-live-ops input{background:#020617;border:1px solid #f59e0b66;border-radius:12px;color:#f8fafc;font:inherit;padding:10px 12px}.absolute-voice-live-ops__actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.absolute-voice-live-ops__actions button{background:#f59e0b;border:0;border-radius:999px;color:#111827;cursor:pointer;font:inherit;font-weight:900;padding:8px 12px}.absolute-voice-live-ops__actions button:disabled{cursor:not-allowed;opacity:.5}.absolute-voice-live-ops__error{color:#fecaca;font-weight:800}`;
3869
+ var mountVoiceLiveOps = (element, options = {}) => {
3870
+ const store = createVoiceLiveOpsStore(options);
3871
+ let assignee = options.defaultAssignee ?? "operator";
3872
+ let detail = options.defaultDetail ?? "Operator marked this live session.";
3873
+ let tag = options.defaultTag ?? "live-ops";
3874
+ const syncInputs = () => {
3875
+ const assigneeInput = element.querySelector("[data-absolute-voice-live-ops-assignee]");
3876
+ const detailInput = element.querySelector("[data-absolute-voice-live-ops-detail]");
3877
+ const tagInput = element.querySelector("[data-absolute-voice-live-ops-tag]");
3878
+ if (assigneeInput instanceof HTMLInputElement) {
3879
+ assignee = assigneeInput.value;
3880
+ }
3881
+ if (detailInput instanceof HTMLInputElement) {
3882
+ detail = detailInput.value;
3883
+ }
3884
+ if (tagInput instanceof HTMLInputElement) {
3885
+ tag = tagInput.value;
3886
+ }
3887
+ };
3888
+ const render = () => {
3889
+ element.innerHTML = renderVoiceLiveOpsHTML(store.getSnapshot(), {
3890
+ ...options,
3891
+ defaultAssignee: assignee,
3892
+ defaultDetail: detail,
3893
+ defaultTag: tag
3894
+ });
3895
+ };
3896
+ const unsubscribe = store.subscribe(render);
3897
+ const handleInput = () => syncInputs();
3898
+ const handleClick = (event) => {
3899
+ const target = event.target;
3900
+ if (!(target instanceof Element)) {
3901
+ return;
3902
+ }
3903
+ const button = target.closest("[data-absolute-voice-live-ops-action]");
3904
+ const action = button?.getAttribute("data-absolute-voice-live-ops-action");
3905
+ if (!action) {
3906
+ return;
3907
+ }
3908
+ syncInputs();
3909
+ store.run(createVoiceLiveOpsInput(action, {
3910
+ assignee,
3911
+ detail,
3912
+ sessionId: options.getSessionId?.(),
3913
+ tag
3914
+ })).catch(() => {});
3915
+ };
3916
+ element.addEventListener?.("click", handleClick);
3917
+ element.addEventListener?.("input", handleInput);
3918
+ render();
3919
+ return {
3920
+ close: () => {
3921
+ element.removeEventListener?.("click", handleClick);
3922
+ element.removeEventListener?.("input", handleInput);
3923
+ unsubscribe();
3924
+ store.close();
3925
+ },
3926
+ run: store.run
3927
+ };
3928
+ };
3929
+ var defineVoiceLiveOpsElement = (tagName = "absolute-voice-live-ops", options = {}) => {
3930
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
3931
+ return;
3932
+ }
3933
+ customElements.define(tagName, class AbsoluteVoiceLiveOpsElement extends HTMLElement {
3934
+ mounted;
3935
+ connectedCallback() {
3936
+ this.mounted = mountVoiceLiveOps(this, {
3937
+ ...options,
3938
+ description: this.getAttribute("description") ?? options.description,
3939
+ getSessionId: options.getSessionId ?? (() => this.getAttribute("session-id") ?? undefined),
3940
+ title: this.getAttribute("title") ?? options.title
3941
+ });
3942
+ }
3943
+ disconnectedCallback() {
3944
+ this.mounted?.close();
3945
+ this.mounted = undefined;
3946
+ }
3947
+ });
3948
+ };
2676
3949
  // src/client/opsActionHistoryWidget.ts
2677
- var escapeHtml3 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3950
+ var escapeHtml5 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2678
3951
  var renderVoiceOpsActionHistoryWidgetHTML = (snapshot, options = {}) => {
2679
3952
  const report = snapshot.report;
2680
3953
  const entries = (report?.entries ?? []).slice(0, options.limit ?? 5);
2681
- const rows = entries.map((entry) => `<li class="absolute-voice-ops-action-history__entry absolute-voice-ops-action-history__entry--${entry.ok ? "success" : "error"}"><span>${escapeHtml3(entry.actionId)}</span><strong>${escapeHtml3(entry.ok ? "Success" : "Failed")}</strong><small>${escapeHtml3(new Date(entry.at).toLocaleString())}${entry.status ? ` \xB7 HTTP ${String(entry.status)}` : ""}</small></li>`).join("");
3954
+ const rows = entries.map((entry) => `<li class="absolute-voice-ops-action-history__entry absolute-voice-ops-action-history__entry--${entry.ok ? "success" : "error"}"><span>${escapeHtml5(entry.actionId)}</span><strong>${escapeHtml5(entry.ok ? "Success" : "Failed")}</strong><small>${escapeHtml5(new Date(entry.at).toLocaleString())}${entry.status ? ` \xB7 HTTP ${String(entry.status)}` : ""}</small></li>`).join("");
2682
3955
  return `<section class="absolute-voice-ops-action-history">
2683
- <header><span>Operator proof</span><strong>${escapeHtml3(options.title ?? "Action History")}</strong></header>
3956
+ <header><span>Operator proof</span><strong>${escapeHtml5(options.title ?? "Action History")}</strong></header>
2684
3957
  <p>${String(report?.total ?? 0)} action(s), ${String(report?.failed ?? 0)} failed.</p>
2685
3958
  <ul>${rows || "<li>No operator actions recorded yet.</li>"}</ul>
2686
- ${snapshot.error ? `<p class="absolute-voice-ops-action-history__error">${escapeHtml3(snapshot.error)}</p>` : ""}
3959
+ ${snapshot.error ? `<p class="absolute-voice-ops-action-history__error">${escapeHtml5(snapshot.error)}</p>` : ""}
2687
3960
  </section>`;
2688
3961
  };
2689
3962
  var getVoiceOpsActionHistoryCSS = () => `.absolute-voice-ops-action-history{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;font-family:inherit}.absolute-voice-ops-action-history header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-ops-action-history header span{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-ops-action-history header strong{font-size:24px}.absolute-voice-ops-action-history p{color:#514733}.absolute-voice-ops-action-history ul{display:grid;gap:8px;list-style:none;margin:12px 0 0;padding:0}.absolute-voice-ops-action-history__entry{background:#fff;border:1px solid #eee4d2;border-radius:14px;display:grid;gap:3px;padding:10px 12px}.absolute-voice-ops-action-history__entry--error{border-color:#f2a7a7}.absolute-voice-ops-action-history__entry span{font-weight:800}.absolute-voice-ops-action-history__entry small{color:#655944}.absolute-voice-ops-action-history__error{color:#9f1239;font-weight:700}`;
@@ -2706,7 +3979,7 @@ var mountVoiceOpsActionHistory = (element, path = "/api/voice/ops-actions/histor
2706
3979
  // src/client/deliveryRuntimeWidget.ts
2707
3980
  var DEFAULT_TITLE3 = "Voice Delivery Runtime";
2708
3981
  var DEFAULT_DESCRIPTION3 = "Audit and trace delivery worker health from your AbsoluteJS voice app.";
2709
- var escapeHtml4 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3982
+ var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2710
3983
  var createSurface = (id, summary) => {
2711
3984
  if (!summary) {
2712
3985
  return {
@@ -2755,26 +4028,26 @@ var createVoiceDeliveryRuntimeViewModel = (snapshot, options = {}) => {
2755
4028
  };
2756
4029
  var renderVoiceDeliveryRuntimeHTML = (snapshot, options = {}) => {
2757
4030
  const model = createVoiceDeliveryRuntimeViewModel(snapshot, options);
2758
- const surfaces = model.surfaces.map((surface) => `<li class="absolute-voice-delivery-runtime__surface absolute-voice-delivery-runtime__surface--${escapeHtml4(surface.status)}">
2759
- <span>${escapeHtml4(surface.label)}</span>
2760
- <strong>${escapeHtml4(surface.detail)}</strong>
4031
+ const surfaces = model.surfaces.map((surface) => `<li class="absolute-voice-delivery-runtime__surface absolute-voice-delivery-runtime__surface--${escapeHtml6(surface.status)}">
4032
+ <span>${escapeHtml6(surface.label)}</span>
4033
+ <strong>${escapeHtml6(surface.detail)}</strong>
2761
4034
  <small>${String(surface.failed)} failed &middot; ${String(surface.deadLettered)} dead-lettered</small>
2762
4035
  </li>`).join("");
2763
4036
  const actions = options.includeActions === false ? "" : `<div class="absolute-voice-delivery-runtime__actions">
2764
4037
  <button type="button" data-absolute-voice-delivery-runtime-action="tick">${model.actionStatus === "running" ? "Working..." : "Tick workers"}</button>
2765
4038
  <button type="button" data-absolute-voice-delivery-runtime-action="requeue-dead-letters"${model.surfaces.some((surface) => surface.deadLettered > 0) ? "" : " disabled"}>Requeue dead letters</button>
2766
4039
  </div>`;
2767
- const actionError = model.actionError ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml4(model.actionError)}</p>` : "";
2768
- return `<section class="absolute-voice-delivery-runtime absolute-voice-delivery-runtime--${escapeHtml4(model.status)}">
4040
+ const actionError = model.actionError ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml6(model.actionError)}</p>` : "";
4041
+ return `<section class="absolute-voice-delivery-runtime absolute-voice-delivery-runtime--${escapeHtml6(model.status)}">
2769
4042
  <header class="absolute-voice-delivery-runtime__header">
2770
- <span class="absolute-voice-delivery-runtime__eyebrow">${escapeHtml4(model.title)}</span>
2771
- <strong class="absolute-voice-delivery-runtime__label">${escapeHtml4(model.label)}</strong>
4043
+ <span class="absolute-voice-delivery-runtime__eyebrow">${escapeHtml6(model.title)}</span>
4044
+ <strong class="absolute-voice-delivery-runtime__label">${escapeHtml6(model.label)}</strong>
2772
4045
  </header>
2773
- <p class="absolute-voice-delivery-runtime__description">${escapeHtml4(model.description)}</p>
4046
+ <p class="absolute-voice-delivery-runtime__description">${escapeHtml6(model.description)}</p>
2774
4047
  <ul class="absolute-voice-delivery-runtime__surfaces">${surfaces}</ul>
2775
4048
  ${actions}
2776
4049
  ${actionError}
2777
- ${model.error ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml4(model.error)}</p>` : ""}
4050
+ ${model.error ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml6(model.error)}</p>` : ""}
2778
4051
  </section>`;
2779
4052
  };
2780
4053
  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}`;
@@ -2912,7 +4185,7 @@ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {})
2912
4185
  // src/client/routingStatusWidget.ts
2913
4186
  var DEFAULT_TITLE4 = "Voice Routing";
2914
4187
  var DEFAULT_DESCRIPTION4 = "Latest provider routing decision from the self-hosted trace store.";
2915
- var escapeHtml5 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4188
+ var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2916
4189
  var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
2917
4190
  var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
2918
4191
  const decision = snapshot.decision;
@@ -2949,17 +4222,17 @@ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
2949
4222
  var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
2950
4223
  const model = createVoiceRoutingStatusViewModel(snapshot, options);
2951
4224
  const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
2952
- <span>${escapeHtml5(row.label)}</span>
2953
- <strong>${escapeHtml5(row.value)}</strong>
4225
+ <span>${escapeHtml7(row.label)}</span>
4226
+ <strong>${escapeHtml7(row.value)}</strong>
2954
4227
  </div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
2955
- return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml5(model.status)}">
4228
+ return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml7(model.status)}">
2956
4229
  <header class="absolute-voice-routing-status__header">
2957
- <span class="absolute-voice-routing-status__eyebrow">${escapeHtml5(model.title)}</span>
2958
- <strong class="absolute-voice-routing-status__label">${escapeHtml5(model.label)}</strong>
4230
+ <span class="absolute-voice-routing-status__eyebrow">${escapeHtml7(model.title)}</span>
4231
+ <strong class="absolute-voice-routing-status__label">${escapeHtml7(model.label)}</strong>
2959
4232
  </header>
2960
- <p class="absolute-voice-routing-status__description">${escapeHtml5(model.description)}</p>
4233
+ <p class="absolute-voice-routing-status__description">${escapeHtml7(model.description)}</p>
2961
4234
  ${rows}
2962
- ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml5(model.error)}</p>` : ""}
4235
+ ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml7(model.error)}</p>` : ""}
2963
4236
  </section>`;
2964
4237
  };
2965
4238
  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}`;
@@ -3685,7 +4958,7 @@ var createVoiceProviderSimulationControlsStore = (options) => {
3685
4958
  };
3686
4959
  };
3687
4960
  // src/client/providerSimulationControlsWidget.ts
3688
- var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4961
+ var escapeHtml8 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3689
4962
  var formatKind = (kind) => (kind ?? "stt").toUpperCase();
3690
4963
  var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
3691
4964
  const configuredProviders = options.providers.filter((provider) => provider.configured !== false);
@@ -3705,18 +4978,18 @@ var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
3705
4978
  };
3706
4979
  var renderVoiceProviderSimulationControlsHTML = (snapshot, options) => {
3707
4980
  const model = createVoiceProviderSimulationControlsViewModel(snapshot, options);
3708
- 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("");
3709
- 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("");
4981
+ const failureButtons = model.failureProviders.map((provider) => `<button type="button" data-voice-provider-fail="${escapeHtml8(provider.provider)}"${!model.canSimulateFailure || snapshot.isRunning ? " disabled" : ""}>Simulate ${escapeHtml8(provider.provider)} ${escapeHtml8(formatKind(options.kind))} failure</button>`).join("");
4982
+ const recoveryButtons = model.providers.map((provider) => `<button type="button" data-voice-provider-recover="${escapeHtml8(provider.provider)}"${snapshot.isRunning ? " disabled" : ""}>Mark ${escapeHtml8(provider.provider)} recovered</button>`).join("");
3710
4983
  return `<section class="absolute-voice-provider-simulation absolute-voice-provider-simulation--${snapshot.error ? "error" : snapshot.isRunning ? "running" : "ready"}">
3711
4984
  <header class="absolute-voice-provider-simulation__header">
3712
- <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml6(model.title)}</span>
3713
- <strong class="absolute-voice-provider-simulation__label">${escapeHtml6(model.label)}</strong>
4985
+ <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml8(model.title)}</span>
4986
+ <strong class="absolute-voice-provider-simulation__label">${escapeHtml8(model.label)}</strong>
3714
4987
  </header>
3715
- <p class="absolute-voice-provider-simulation__description">${escapeHtml6(model.description)}</p>
3716
- ${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml6(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
4988
+ <p class="absolute-voice-provider-simulation__description">${escapeHtml8(model.description)}</p>
4989
+ ${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml8(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
3717
4990
  <div class="absolute-voice-provider-simulation__actions">${failureButtons}${recoveryButtons}</div>
3718
- ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml6(snapshot.error)}</p>` : ""}
3719
- ${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml6(model.resultText)}</pre>` : ""}
4991
+ ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml8(snapshot.error)}</p>` : ""}
4992
+ ${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml8(model.resultText)}</pre>` : ""}
3720
4993
  </section>`;
3721
4994
  };
3722
4995
  var bindVoiceProviderSimulationControls = (element, store) => {
@@ -3783,7 +5056,7 @@ var defineVoiceProviderSimulationControlsElement = (tagName = "absolute-voice-pr
3783
5056
  // src/client/providerStatusWidget.ts
3784
5057
  var DEFAULT_TITLE5 = "Voice Providers";
3785
5058
  var DEFAULT_DESCRIPTION5 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
3786
- var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5059
+ var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3787
5060
  var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
3788
5061
  var formatStatus = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
3789
5062
  var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
@@ -3839,25 +5112,25 @@ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
3839
5112
  };
3840
5113
  var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
3841
5114
  const model = createVoiceProviderStatusViewModel(snapshot, options);
3842
- const providers = model.providers.length ? `<div class="absolute-voice-provider-status__providers">${model.providers.map((provider) => `<article class="absolute-voice-provider-status__provider absolute-voice-provider-status__provider--${escapeHtml7(provider.status)}">
5115
+ 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)}">
3843
5116
  <header>
3844
- <strong>${escapeHtml7(provider.label)}</strong>
3845
- <span>${escapeHtml7(formatStatus(provider.status))}</span>
5117
+ <strong>${escapeHtml9(provider.label)}</strong>
5118
+ <span>${escapeHtml9(formatStatus(provider.status))}</span>
3846
5119
  </header>
3847
- <p>${escapeHtml7(provider.detail)}</p>
5120
+ <p>${escapeHtml9(provider.detail)}</p>
3848
5121
  <dl>${provider.rows.map((row) => `<div>
3849
- <dt>${escapeHtml7(row.label)}</dt>
3850
- <dd>${escapeHtml7(row.value)}</dd>
5122
+ <dt>${escapeHtml9(row.label)}</dt>
5123
+ <dd>${escapeHtml9(row.value)}</dd>
3851
5124
  </div>`).join("")}</dl>
3852
5125
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
3853
- return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml7(model.status)}">
5126
+ return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml9(model.status)}">
3854
5127
  <header class="absolute-voice-provider-status__header">
3855
- <span class="absolute-voice-provider-status__eyebrow">${escapeHtml7(model.title)}</span>
3856
- <strong class="absolute-voice-provider-status__label">${escapeHtml7(model.label)}</strong>
5128
+ <span class="absolute-voice-provider-status__eyebrow">${escapeHtml9(model.title)}</span>
5129
+ <strong class="absolute-voice-provider-status__label">${escapeHtml9(model.label)}</strong>
3857
5130
  </header>
3858
- <p class="absolute-voice-provider-status__description">${escapeHtml7(model.description)}</p>
5131
+ <p class="absolute-voice-provider-status__description">${escapeHtml9(model.description)}</p>
3859
5132
  ${providers}
3860
- ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml7(model.error)}</p>` : ""}
5133
+ ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml9(model.error)}</p>` : ""}
3861
5134
  </section>`;
3862
5135
  };
3863
5136
  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}`;
@@ -3900,7 +5173,7 @@ var defineVoiceProviderStatusElement = (tagName = "absolute-voice-provider-statu
3900
5173
  // src/client/providerCapabilitiesWidget.ts
3901
5174
  var DEFAULT_TITLE6 = "Provider Capabilities";
3902
5175
  var DEFAULT_DESCRIPTION6 = "Configured, selected, and healthy voice providers for this deployment.";
3903
- var escapeHtml8 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5176
+ var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3904
5177
  var formatProvider2 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
3905
5178
  var formatKind2 = (kind) => kind.toUpperCase();
3906
5179
  var formatStatus2 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
@@ -3955,25 +5228,25 @@ var createVoiceProviderCapabilitiesViewModel = (snapshot, options = {}) => {
3955
5228
  };
3956
5229
  var renderVoiceProviderCapabilitiesHTML = (snapshot, options = {}) => {
3957
5230
  const model = createVoiceProviderCapabilitiesViewModel(snapshot, options);
3958
- const capabilities = model.capabilities.length ? `<div class="absolute-voice-provider-capabilities__providers">${model.capabilities.map((capability) => `<article class="absolute-voice-provider-capabilities__provider absolute-voice-provider-capabilities__provider--${escapeHtml8(capability.status)}">
5231
+ 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--${escapeHtml10(capability.status)}">
3959
5232
  <header>
3960
- <strong>${escapeHtml8(capability.label)}</strong>
3961
- <span>${escapeHtml8(formatStatus2(capability.status))}</span>
5233
+ <strong>${escapeHtml10(capability.label)}</strong>
5234
+ <span>${escapeHtml10(formatStatus2(capability.status))}</span>
3962
5235
  </header>
3963
- <p>${escapeHtml8(capability.detail)}</p>
5236
+ <p>${escapeHtml10(capability.detail)}</p>
3964
5237
  <dl>${capability.rows.map((row) => `<div>
3965
- <dt>${escapeHtml8(row.label)}</dt>
3966
- <dd>${escapeHtml8(row.value)}</dd>
5238
+ <dt>${escapeHtml10(row.label)}</dt>
5239
+ <dd>${escapeHtml10(row.value)}</dd>
3967
5240
  </div>`).join("")}</dl>
3968
5241
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-capabilities__empty">Configure provider capabilities to see deployment coverage.</p>';
3969
- return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml8(model.status)}">
5242
+ return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml10(model.status)}">
3970
5243
  <header class="absolute-voice-provider-capabilities__header">
3971
- <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml8(model.title)}</span>
3972
- <strong class="absolute-voice-provider-capabilities__label">${escapeHtml8(model.label)}</strong>
5244
+ <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml10(model.title)}</span>
5245
+ <strong class="absolute-voice-provider-capabilities__label">${escapeHtml10(model.label)}</strong>
3973
5246
  </header>
3974
- <p class="absolute-voice-provider-capabilities__description">${escapeHtml8(model.description)}</p>
5247
+ <p class="absolute-voice-provider-capabilities__description">${escapeHtml10(model.description)}</p>
3975
5248
  ${capabilities}
3976
- ${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml8(model.error)}</p>` : ""}
5249
+ ${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml10(model.error)}</p>` : ""}
3977
5250
  </section>`;
3978
5251
  };
3979
5252
  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}`;
@@ -4016,7 +5289,7 @@ var defineVoiceProviderCapabilitiesElement = (tagName = "absolute-voice-provider
4016
5289
  // src/client/providerContractsWidget.ts
4017
5290
  var DEFAULT_TITLE7 = "Provider Contracts";
4018
5291
  var DEFAULT_DESCRIPTION7 = "Production contract coverage for provider env, latency, fallback, streaming, and capabilities.";
4019
- var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5292
+ var escapeHtml11 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4020
5293
  var formatProvider3 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
4021
5294
  var formatStatus3 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
4022
5295
  var contractDetail = (row) => {
@@ -4060,26 +5333,26 @@ var createVoiceProviderContractsViewModel = (snapshot, options = {}) => {
4060
5333
  };
4061
5334
  var renderVoiceProviderContractsHTML = (snapshot, options = {}) => {
4062
5335
  const model = createVoiceProviderContractsViewModel(snapshot, options);
4063
- 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--${escapeHtml9(row.status)}">
5336
+ 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--${escapeHtml11(row.status)}">
4064
5337
  <header>
4065
- <strong>${escapeHtml9(row.label)}</strong>
4066
- <span>${escapeHtml9(formatStatus3(row.status))}</span>
5338
+ <strong>${escapeHtml11(row.label)}</strong>
5339
+ <span>${escapeHtml11(formatStatus3(row.status))}</span>
4067
5340
  </header>
4068
- <p>${escapeHtml9(row.detail)}</p>
4069
- ${row.remediations.length ? `<ul class="absolute-voice-provider-contracts__remediations">${row.remediations.map((remediation) => `<li>${remediation.href ? `<a href="${escapeHtml9(remediation.href)}">${escapeHtml9(remediation.label)}</a>` : `<strong>${escapeHtml9(remediation.label)}</strong>`}<span>${escapeHtml9(remediation.detail)}</span></li>`).join("")}</ul>` : ""}
5341
+ <p>${escapeHtml11(row.detail)}</p>
5342
+ ${row.remediations.length ? `<ul class="absolute-voice-provider-contracts__remediations">${row.remediations.map((remediation) => `<li>${remediation.href ? `<a href="${escapeHtml11(remediation.href)}">${escapeHtml11(remediation.label)}</a>` : `<strong>${escapeHtml11(remediation.label)}</strong>`}<span>${escapeHtml11(remediation.detail)}</span></li>`).join("")}</ul>` : ""}
4070
5343
  <dl>${row.rows.map((item) => `<div>
4071
- <dt>${escapeHtml9(item.label)}</dt>
4072
- <dd>${escapeHtml9(item.value)}</dd>
5344
+ <dt>${escapeHtml11(item.label)}</dt>
5345
+ <dd>${escapeHtml11(item.value)}</dd>
4073
5346
  </div>`).join("")}</dl>
4074
5347
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-contracts__empty">Configure provider contracts to see production coverage.</p>';
4075
- return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${escapeHtml9(model.status)}">
5348
+ return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${escapeHtml11(model.status)}">
4076
5349
  <header class="absolute-voice-provider-contracts__header">
4077
- <span class="absolute-voice-provider-contracts__eyebrow">${escapeHtml9(model.title)}</span>
4078
- <strong class="absolute-voice-provider-contracts__label">${escapeHtml9(model.label)}</strong>
5350
+ <span class="absolute-voice-provider-contracts__eyebrow">${escapeHtml11(model.title)}</span>
5351
+ <strong class="absolute-voice-provider-contracts__label">${escapeHtml11(model.label)}</strong>
4079
5352
  </header>
4080
- <p class="absolute-voice-provider-contracts__description">${escapeHtml9(model.description)}</p>
5353
+ <p class="absolute-voice-provider-contracts__description">${escapeHtml11(model.description)}</p>
4081
5354
  ${rows}
4082
- ${model.error ? `<p class="absolute-voice-provider-contracts__error">${escapeHtml9(model.error)}</p>` : ""}
5355
+ ${model.error ? `<p class="absolute-voice-provider-contracts__error">${escapeHtml11(model.error)}</p>` : ""}
4083
5356
  </section>`;
4084
5357
  };
4085
5358
  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}`;
@@ -4122,7 +5395,7 @@ var defineVoiceProviderContractsElement = (tagName = "absolute-voice-provider-co
4122
5395
  // src/client/turnQualityWidget.ts
4123
5396
  var DEFAULT_TITLE8 = "Turn Quality";
4124
5397
  var DEFAULT_DESCRIPTION8 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
4125
- var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5398
+ var escapeHtml12 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4126
5399
  var formatConfidence = (value) => typeof value === "number" ? `${Math.round(value * 100)}%` : "n/a";
4127
5400
  var formatMaybe = (value) => value === undefined || value === "" ? "n/a" : String(value);
4128
5401
  var getTurnDetail = (turn) => {
@@ -4172,25 +5445,25 @@ var createVoiceTurnQualityViewModel = (snapshot, options = {}) => {
4172
5445
  };
4173
5446
  var renderVoiceTurnQualityHTML = (snapshot, options = {}) => {
4174
5447
  const model = createVoiceTurnQualityViewModel(snapshot, options);
4175
- 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--${escapeHtml10(turn.status)}">
5448
+ 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)}">
4176
5449
  <header>
4177
- <strong>${escapeHtml10(turn.label)}</strong>
4178
- <span>${escapeHtml10(turn.status)}</span>
5450
+ <strong>${escapeHtml12(turn.label)}</strong>
5451
+ <span>${escapeHtml12(turn.status)}</span>
4179
5452
  </header>
4180
- <p>${escapeHtml10(turn.detail)}</p>
5453
+ <p>${escapeHtml12(turn.detail)}</p>
4181
5454
  <dl>${turn.rows.map((row) => `<div>
4182
- <dt>${escapeHtml10(row.label)}</dt>
4183
- <dd>${escapeHtml10(row.value)}</dd>
5455
+ <dt>${escapeHtml12(row.label)}</dt>
5456
+ <dd>${escapeHtml12(row.value)}</dd>
4184
5457
  </div>`).join("")}</dl>
4185
5458
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-quality__empty">Complete a voice turn to see STT quality diagnostics.</p>';
4186
- return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml10(model.status)}">
5459
+ return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml12(model.status)}">
4187
5460
  <header class="absolute-voice-turn-quality__header">
4188
- <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml10(model.title)}</span>
4189
- <strong class="absolute-voice-turn-quality__label">${escapeHtml10(model.label)}</strong>
5461
+ <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml12(model.title)}</span>
5462
+ <strong class="absolute-voice-turn-quality__label">${escapeHtml12(model.label)}</strong>
4190
5463
  </header>
4191
- <p class="absolute-voice-turn-quality__description">${escapeHtml10(model.description)}</p>
5464
+ <p class="absolute-voice-turn-quality__description">${escapeHtml12(model.description)}</p>
4192
5465
  ${turns}
4193
- ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml10(model.error)}</p>` : ""}
5466
+ ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml12(model.error)}</p>` : ""}
4194
5467
  </section>`;
4195
5468
  };
4196
5469
  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}`;
@@ -4234,7 +5507,7 @@ var defineVoiceTurnQualityElement = (tagName = "absolute-voice-turn-quality") =>
4234
5507
  var DEFAULT_TITLE9 = "Turn Latency";
4235
5508
  var DEFAULT_DESCRIPTION9 = "Per-turn timing from first transcript to commit and assistant response start.";
4236
5509
  var DEFAULT_PROOF_LABEL = "Run latency proof";
4237
- var escapeHtml11 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5510
+ var escapeHtml13 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4238
5511
  var formatMs = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
4239
5512
  var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
4240
5513
  const turns = (snapshot.report?.turns ?? []).map((turn) => ({
@@ -4262,25 +5535,25 @@ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
4262
5535
  };
4263
5536
  var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
4264
5537
  const model = createVoiceTurnLatencyViewModel(snapshot, options);
4265
- 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)}">
5538
+ 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--${escapeHtml13(turn.status)}">
4266
5539
  <header>
4267
- <strong>${escapeHtml11(turn.label)}</strong>
4268
- <span>${escapeHtml11(turn.status)}</span>
5540
+ <strong>${escapeHtml13(turn.label)}</strong>
5541
+ <span>${escapeHtml13(turn.status)}</span>
4269
5542
  </header>
4270
5543
  <dl>${turn.rows.map((row) => `<div>
4271
- <dt>${escapeHtml11(row.label)}</dt>
4272
- <dd>${escapeHtml11(row.value)}</dd>
5544
+ <dt>${escapeHtml13(row.label)}</dt>
5545
+ <dd>${escapeHtml13(row.value)}</dd>
4273
5546
  </div>`).join("")}</dl>
4274
5547
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-latency__empty">Complete a voice turn to see latency diagnostics.</p>';
4275
- return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml11(model.status)}">
5548
+ return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml13(model.status)}">
4276
5549
  <header class="absolute-voice-turn-latency__header">
4277
- <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml11(model.title)}</span>
4278
- <strong class="absolute-voice-turn-latency__label">${escapeHtml11(model.label)}</strong>
5550
+ <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml13(model.title)}</span>
5551
+ <strong class="absolute-voice-turn-latency__label">${escapeHtml13(model.label)}</strong>
4279
5552
  </header>
4280
- <p class="absolute-voice-turn-latency__description">${escapeHtml11(model.description)}</p>
4281
- ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml11(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
5553
+ <p class="absolute-voice-turn-latency__description">${escapeHtml13(model.description)}</p>
5554
+ ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml13(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
4282
5555
  ${turns}
4283
- ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml11(model.error)}</p>` : ""}
5556
+ ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml13(model.error)}</p>` : ""}
4284
5557
  </section>`;
4285
5558
  };
4286
5559
  var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {}) => {
@@ -4332,7 +5605,7 @@ var defineVoiceTurnLatencyElement = (tagName = "absolute-voice-turn-latency") =>
4332
5605
  // src/client/traceTimelineWidget.ts
4333
5606
  var DEFAULT_TITLE10 = "Voice Traces";
4334
5607
  var DEFAULT_DESCRIPTION10 = "Latest call timelines with provider latency, fallbacks, handoffs, and errors from your self-hosted trace store.";
4335
- var escapeHtml12 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5608
+ var escapeHtml14 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4336
5609
  var formatMs2 = (value) => typeof value === "number" ? `${value}ms` : "n/a";
4337
5610
  var formatProviders = (session) => session.providers.length ? session.providers.map((provider) => provider.provider).join(", ") : "No providers";
4338
5611
  var createVoiceTraceTimelineViewModel = (snapshot, options = {}) => {
@@ -4362,27 +5635,27 @@ var renderVoiceTraceTimelineWidgetHTML = (snapshot, options = {}) => {
4362
5635
  const model = createVoiceTraceTimelineViewModel(snapshot, options);
4363
5636
  const sessions = model.sessions.length ? `<div class="absolute-voice-trace-timeline__sessions">${model.sessions.map((session) => {
4364
5637
  const supportLinks = [
4365
- `<a href="${escapeHtml12(session.detailHref)}">Open timeline</a>`,
4366
- session.operationsRecordHref ? `<a href="${escapeHtml12(session.operationsRecordHref)}">Open operations record</a>` : undefined,
4367
- session.incidentBundleHref ? `<a href="${escapeHtml12(session.incidentBundleHref)}">Export incident bundle</a>` : undefined
5638
+ `<a href="${escapeHtml14(session.detailHref)}">Open timeline</a>`,
5639
+ session.operationsRecordHref ? `<a href="${escapeHtml14(session.operationsRecordHref)}">Open operations record</a>` : undefined,
5640
+ session.incidentBundleHref ? `<a href="${escapeHtml14(session.incidentBundleHref)}">Export incident bundle</a>` : undefined
4368
5641
  ].filter(Boolean).join("");
4369
- return `<article class="absolute-voice-trace-timeline__session absolute-voice-trace-timeline__session--${escapeHtml12(session.status)}">
5642
+ return `<article class="absolute-voice-trace-timeline__session absolute-voice-trace-timeline__session--${escapeHtml14(session.status)}">
4370
5643
  <header>
4371
- <strong>${escapeHtml12(session.sessionId)}</strong>
4372
- <span>${escapeHtml12(session.status)}</span>
5644
+ <strong>${escapeHtml14(session.sessionId)}</strong>
5645
+ <span>${escapeHtml14(session.status)}</span>
4373
5646
  </header>
4374
- <p>${escapeHtml12(session.label)} \xB7 ${escapeHtml12(session.durationLabel)} \xB7 ${escapeHtml12(session.providerLabel)}</p>
5647
+ <p>${escapeHtml14(session.label)} \xB7 ${escapeHtml14(session.durationLabel)} \xB7 ${escapeHtml14(session.providerLabel)}</p>
4375
5648
  <p class="absolute-voice-trace-timeline__actions">${supportLinks}</p>
4376
5649
  </article>`;
4377
5650
  }).join("")}</div>` : '<p class="absolute-voice-trace-timeline__empty">Run a voice session to see call timelines.</p>';
4378
- return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml12(model.status)}">
5651
+ return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml14(model.status)}">
4379
5652
  <header class="absolute-voice-trace-timeline__header">
4380
- <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml12(model.title)}</span>
4381
- <strong class="absolute-voice-trace-timeline__label">${escapeHtml12(model.label)}</strong>
5653
+ <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml14(model.title)}</span>
5654
+ <strong class="absolute-voice-trace-timeline__label">${escapeHtml14(model.label)}</strong>
4382
5655
  </header>
4383
- <p class="absolute-voice-trace-timeline__description">${escapeHtml12(model.description)}</p>
5656
+ <p class="absolute-voice-trace-timeline__description">${escapeHtml14(model.description)}</p>
4384
5657
  ${sessions}
4385
- ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml12(model.error)}</p>` : ""}
5658
+ ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml14(model.error)}</p>` : ""}
4386
5659
  </section>`;
4387
5660
  };
4388
5661
  var getVoiceTraceTimelineCSS = () => `.absolute-voice-trace-timeline{border:1px solid #bad7d3;border-radius:20px;background:#f3fffb;color:#09201c;padding:18px;box-shadow:0 18px 40px rgba(9,32,28,.12);font-family:inherit}.absolute-voice-trace-timeline--error,.absolute-voice-trace-timeline--failed{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-trace-timeline--warning{border-color:#fbbf24;background:#fffaf0}.absolute-voice-trace-timeline__header,.absolute-voice-trace-timeline__session header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-trace-timeline__eyebrow{color:#17665b;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-trace-timeline__label{font-size:24px;line-height:1}.absolute-voice-trace-timeline__description,.absolute-voice-trace-timeline__session p,.absolute-voice-trace-timeline__empty{color:#35544f}.absolute-voice-trace-timeline__sessions{display:grid;gap:12px;margin-top:14px}.absolute-voice-trace-timeline__session{background:#fff;border:1px solid #cfe7e2;border-radius:16px;padding:14px}.absolute-voice-trace-timeline__session--failed{border-color:#f2a7a7}.absolute-voice-trace-timeline__session--warning{border-color:#fbbf24}.absolute-voice-trace-timeline__session p{margin:10px 0}.absolute-voice-trace-timeline__actions{display:flex;flex-wrap:wrap;gap:10px}.absolute-voice-trace-timeline__session a{color:#0f766e;font-weight:800}.absolute-voice-trace-timeline__empty{margin:14px 0 0}.absolute-voice-trace-timeline__error{color:#9f1239;font-weight:700}`;
@@ -4521,8 +5794,10 @@ export {
4521
5794
  renderVoiceOpsStatusHTML,
4522
5795
  renderVoiceOpsActionHistoryWidgetHTML,
4523
5796
  renderVoiceOpsActionCenterHTML,
5797
+ renderVoiceLiveOpsHTML,
4524
5798
  renderVoiceDeliveryRuntimeHTML,
4525
5799
  recordVoiceOpsActionResult,
5800
+ postVoiceLiveOpsAction,
4526
5801
  mountVoiceTurnQuality,
4527
5802
  mountVoiceTurnLatency,
4528
5803
  mountVoiceTraceTimeline,
@@ -4534,6 +5809,7 @@ export {
4534
5809
  mountVoiceOpsStatus,
4535
5810
  mountVoiceOpsActionHistory,
4536
5811
  mountVoiceOpsActionCenter,
5812
+ mountVoiceLiveOps,
4537
5813
  mountVoiceDeliveryRuntime,
4538
5814
  getVoiceTurnQualityCSS,
4539
5815
  getVoiceTraceTimelineCSS,
@@ -4545,6 +5821,7 @@ export {
4545
5821
  getVoiceOpsStatusCSS,
4546
5822
  getVoiceOpsActionHistoryCSS,
4547
5823
  getVoiceOpsActionCenterCSS,
5824
+ getVoiceLiveOpsCSS,
4548
5825
  getVoiceDeliveryRuntimeCSS,
4549
5826
  fetchVoiceWorkflowStatus,
4550
5827
  fetchVoiceTurnQuality,
@@ -4568,6 +5845,7 @@ export {
4568
5845
  defineVoiceProviderCapabilitiesElement,
4569
5846
  defineVoiceOpsStatusElement,
4570
5847
  defineVoiceOpsActionCenterElement,
5848
+ defineVoiceLiveOpsElement,
4571
5849
  defineVoiceDeliveryRuntimeElement,
4572
5850
  decodeVoiceAudioChunk,
4573
5851
  createVoiceWorkflowStatusStore,
@@ -4595,6 +5873,8 @@ export {
4595
5873
  createVoiceOpsActionCenterStore,
4596
5874
  createVoiceOpsActionCenterActions,
4597
5875
  createVoiceLiveTurnLatencyMonitor,
5876
+ createVoiceLiveOpsStore,
5877
+ createVoiceLiveOpsInput,
4598
5878
  createVoiceDuplexController,
4599
5879
  createVoiceDeliveryRuntimeViewModel,
4600
5880
  createVoiceDeliveryRuntimeStore,