@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.
@@ -760,6 +760,1293 @@ var createVoiceOpsActionCenter = (options = {}) => {
760
760
  subscribe: store.subscribe
761
761
  };
762
762
  };
763
+ // src/client/liveOps.ts
764
+ var postVoiceLiveOpsAction = async (input, options = {}) => {
765
+ if (!input.sessionId) {
766
+ throw new Error("Start a voice session before running live ops actions.");
767
+ }
768
+ const fetchImpl = options.fetch ?? globalThis.fetch;
769
+ const response = await fetchImpl(options.actionPath ?? "/api/voice/live-ops/action", {
770
+ body: JSON.stringify(input),
771
+ headers: {
772
+ "Content-Type": "application/json"
773
+ },
774
+ method: "POST"
775
+ });
776
+ const payload = await response.json().catch(() => null);
777
+ if (!response.ok || !payload?.ok) {
778
+ const message = payload && typeof payload === "object" && "error" in payload ? String(payload.error) : `Voice live ops action failed: HTTP ${response.status}`;
779
+ throw new Error(message);
780
+ }
781
+ return payload;
782
+ };
783
+ var createVoiceLiveOpsStore = (options = {}) => {
784
+ const listeners = new Set;
785
+ let closed = false;
786
+ let snapshot = {
787
+ error: null,
788
+ isRunning: false
789
+ };
790
+ const emit = () => {
791
+ for (const listener of listeners) {
792
+ listener();
793
+ }
794
+ };
795
+ const run = async (input) => {
796
+ if (closed) {
797
+ return snapshot.lastResult;
798
+ }
799
+ snapshot = {
800
+ ...snapshot,
801
+ error: null,
802
+ isRunning: true,
803
+ runningAction: input.action
804
+ };
805
+ emit();
806
+ try {
807
+ const result = await postVoiceLiveOpsAction(input, options);
808
+ await options.onControl?.(result);
809
+ snapshot = {
810
+ ...snapshot,
811
+ error: null,
812
+ isRunning: false,
813
+ lastResult: result,
814
+ runningAction: undefined,
815
+ updatedAt: Date.now()
816
+ };
817
+ emit();
818
+ return result;
819
+ } catch (error) {
820
+ snapshot = {
821
+ ...snapshot,
822
+ error: error instanceof Error ? error.message : String(error),
823
+ isRunning: false,
824
+ runningAction: undefined,
825
+ updatedAt: Date.now()
826
+ };
827
+ emit();
828
+ throw error;
829
+ }
830
+ };
831
+ const close = () => {
832
+ closed = true;
833
+ listeners.clear();
834
+ };
835
+ return {
836
+ close,
837
+ getServerSnapshot: () => snapshot,
838
+ getSnapshot: () => snapshot,
839
+ run,
840
+ subscribe: (listener) => {
841
+ listeners.add(listener);
842
+ return () => {
843
+ listeners.delete(listener);
844
+ };
845
+ }
846
+ };
847
+ };
848
+
849
+ // src/liveOps.ts
850
+ import { Elysia } from "elysia";
851
+
852
+ // src/audit.ts
853
+ var includes = (filter, value) => {
854
+ if (!filter) {
855
+ return true;
856
+ }
857
+ if (!value) {
858
+ return false;
859
+ }
860
+ return Array.isArray(filter) ? filter.includes(value) : filter === value;
861
+ };
862
+ var createVoiceAuditEvent = (event) => ({
863
+ ...event,
864
+ at: event.at ?? Date.now(),
865
+ id: event.id ?? crypto.randomUUID()
866
+ });
867
+ var filterVoiceAuditEvents = (events, filter = {}) => {
868
+ const sorted = events.filter((event) => {
869
+ if (!includes(filter.type, event.type)) {
870
+ return false;
871
+ }
872
+ if (!includes(filter.outcome, event.outcome)) {
873
+ return false;
874
+ }
875
+ if (filter.actorId && event.actor?.id !== filter.actorId) {
876
+ return false;
877
+ }
878
+ if (filter.resourceId && event.resource?.id !== filter.resourceId) {
879
+ return false;
880
+ }
881
+ if (filter.resourceType && event.resource?.type !== filter.resourceType) {
882
+ return false;
883
+ }
884
+ if (filter.sessionId && event.sessionId !== filter.sessionId) {
885
+ return false;
886
+ }
887
+ if (filter.traceId && event.traceId !== filter.traceId) {
888
+ return false;
889
+ }
890
+ if (typeof filter.after === "number" && event.at <= filter.after) {
891
+ return false;
892
+ }
893
+ if (typeof filter.afterOrAt === "number" && event.at < filter.afterOrAt) {
894
+ return false;
895
+ }
896
+ if (typeof filter.before === "number" && event.at >= filter.before) {
897
+ return false;
898
+ }
899
+ if (typeof filter.beforeOrAt === "number" && event.at > filter.beforeOrAt) {
900
+ return false;
901
+ }
902
+ return true;
903
+ }).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
904
+ return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
905
+ };
906
+ var createVoiceMemoryAuditEventStore = () => {
907
+ const events = new Map;
908
+ return {
909
+ append: (event) => {
910
+ const stored = createVoiceAuditEvent(event);
911
+ events.set(stored.id, stored);
912
+ return stored;
913
+ },
914
+ get: (id) => events.get(id),
915
+ list: (filter) => filterVoiceAuditEvents([...events.values()], filter)
916
+ };
917
+ };
918
+ var recordVoiceAuditEvent = (store, event) => store.append(createVoiceAuditEvent(event));
919
+ var recordVoiceProviderAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
920
+ action: `${input.kind}.provider.call`,
921
+ actor: input.actor,
922
+ metadata: input.metadata,
923
+ outcome: input.outcome,
924
+ payload: {
925
+ cost: input.cost,
926
+ elapsedMs: input.elapsedMs,
927
+ error: input.error,
928
+ kind: input.kind,
929
+ model: input.model,
930
+ provider: input.provider
931
+ },
932
+ resource: {
933
+ id: input.provider,
934
+ type: "provider"
935
+ },
936
+ sessionId: input.sessionId,
937
+ traceId: input.traceId,
938
+ type: "provider.call"
939
+ });
940
+ var recordVoiceToolAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
941
+ action: "tool.call",
942
+ actor: input.actor,
943
+ metadata: input.metadata,
944
+ outcome: input.outcome,
945
+ payload: {
946
+ elapsedMs: input.elapsedMs,
947
+ error: input.error,
948
+ toolCallId: input.toolCallId,
949
+ toolName: input.toolName
950
+ },
951
+ resource: {
952
+ id: input.toolName,
953
+ type: "tool"
954
+ },
955
+ sessionId: input.sessionId,
956
+ traceId: input.traceId,
957
+ type: "tool.call"
958
+ });
959
+ var recordVoiceHandoffAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
960
+ action: "handoff",
961
+ actor: input.actor,
962
+ metadata: input.metadata,
963
+ outcome: input.outcome,
964
+ payload: {
965
+ fromAgentId: input.fromAgentId,
966
+ reason: input.reason,
967
+ target: input.target,
968
+ toAgentId: input.toAgentId
969
+ },
970
+ resource: {
971
+ id: input.toAgentId ?? input.target,
972
+ type: "handoff"
973
+ },
974
+ sessionId: input.sessionId,
975
+ traceId: input.traceId,
976
+ type: "handoff"
977
+ });
978
+ var recordVoiceRetentionAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
979
+ action: input.dryRun ? "retention.plan" : "retention.apply",
980
+ actor: input.actor ?? {
981
+ id: "voice-retention",
982
+ kind: "system"
983
+ },
984
+ metadata: input.metadata,
985
+ outcome: "success",
986
+ payload: {
987
+ deletedCount: input.report.deletedCount,
988
+ dryRun: input.dryRun,
989
+ scopes: input.report.scopes
990
+ },
991
+ resource: {
992
+ type: "retention-policy"
993
+ },
994
+ type: "retention.policy"
995
+ });
996
+ var recordVoiceOperatorAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
997
+ action: input.action,
998
+ actor: input.actor,
999
+ metadata: input.metadata,
1000
+ outcome: input.outcome ?? "success",
1001
+ payload: input.payload,
1002
+ resource: input.resource,
1003
+ sessionId: input.sessionId,
1004
+ traceId: input.traceId,
1005
+ type: "operator.action"
1006
+ });
1007
+ var createVoiceAuditLogger = (store) => ({
1008
+ handoff: (input) => recordVoiceHandoffAuditEvent({ ...input, store }),
1009
+ operatorAction: (input) => recordVoiceOperatorAuditEvent({ ...input, store }),
1010
+ providerCall: (input) => recordVoiceProviderAuditEvent({ ...input, store }),
1011
+ record: (event) => recordVoiceAuditEvent(store, event),
1012
+ retention: (input) => recordVoiceRetentionAuditEvent({ ...input, store }),
1013
+ toolCall: (input) => recordVoiceToolAuditEvent({ ...input, store })
1014
+ });
1015
+
1016
+ // src/trace.ts
1017
+ var createVoiceTraceEventId = (event) => [
1018
+ event.sessionId,
1019
+ event.turnId ?? "session",
1020
+ event.type,
1021
+ String(event.at ?? Date.now()),
1022
+ crypto.randomUUID()
1023
+ ].map(encodeURIComponent).join(":");
1024
+ var createVoiceTraceEvent = (event) => ({
1025
+ ...event,
1026
+ at: event.at,
1027
+ id: event.id ?? createVoiceTraceEventId({
1028
+ at: event.at,
1029
+ sessionId: event.sessionId,
1030
+ turnId: event.turnId,
1031
+ type: event.type
1032
+ })
1033
+ });
1034
+ var createVoiceTraceSinkDeliveryId = (events) => {
1035
+ const firstEvent = events[0];
1036
+ return [
1037
+ firstEvent?.sessionId ?? "trace",
1038
+ firstEvent?.traceId ?? "sink",
1039
+ String(firstEvent?.at ?? Date.now()),
1040
+ crypto.randomUUID()
1041
+ ].map(encodeURIComponent).join(":");
1042
+ };
1043
+ var createVoiceTraceSinkDeliveryRecord = (input) => {
1044
+ const createdAt = input.createdAt ?? Date.now();
1045
+ return {
1046
+ createdAt,
1047
+ deliveredAt: input.deliveredAt,
1048
+ deliveryAttempts: input.deliveryAttempts,
1049
+ deliveryError: input.deliveryError,
1050
+ deliveryStatus: input.deliveryStatus ?? "pending",
1051
+ events: input.events,
1052
+ id: input.id ?? createVoiceTraceSinkDeliveryId(input.events),
1053
+ sinkDeliveries: input.sinkDeliveries,
1054
+ updatedAt: input.updatedAt ?? createdAt
1055
+ };
1056
+ };
1057
+ var matchesTraceFilter = (event, filter) => {
1058
+ if (filter.sessionId !== undefined && event.sessionId !== filter.sessionId) {
1059
+ return false;
1060
+ }
1061
+ if (filter.turnId !== undefined && event.turnId !== filter.turnId) {
1062
+ return false;
1063
+ }
1064
+ if (filter.scenarioId !== undefined && event.scenarioId !== filter.scenarioId) {
1065
+ return false;
1066
+ }
1067
+ if (filter.traceId !== undefined && event.traceId !== filter.traceId) {
1068
+ return false;
1069
+ }
1070
+ if (filter.type !== undefined) {
1071
+ const types = Array.isArray(filter.type) ? filter.type : [filter.type];
1072
+ if (!types.includes(event.type)) {
1073
+ return false;
1074
+ }
1075
+ }
1076
+ return true;
1077
+ };
1078
+ var filterVoiceTraceEvents = (events, filter = {}) => {
1079
+ const sorted = events.filter((event) => matchesTraceFilter(event, filter)).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
1080
+ return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
1081
+ };
1082
+ var isPruneTimeMatch = (event, options) => {
1083
+ if (typeof options.before === "number" && event.at >= options.before) {
1084
+ return false;
1085
+ }
1086
+ if (typeof options.beforeOrAt === "number" && event.at > options.beforeOrAt) {
1087
+ return false;
1088
+ }
1089
+ return true;
1090
+ };
1091
+ var selectVoiceTraceEventsForPrune = (events, options = {}) => {
1092
+ let candidates = filterVoiceTraceEvents(events, options.filter).filter((event) => isPruneTimeMatch(event, options));
1093
+ if (typeof options.keepNewest === "number" && options.keepNewest >= 0) {
1094
+ 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));
1095
+ candidates = candidates.filter((event) => !newestIds.has(event.id));
1096
+ }
1097
+ return typeof options.limit === "number" && options.limit >= 0 ? candidates.slice(0, options.limit) : candidates;
1098
+ };
1099
+ var pruneVoiceTraceEvents = async (options) => {
1100
+ const events = await options.store.list(options.filter);
1101
+ const deleted = selectVoiceTraceEventsForPrune(events, options);
1102
+ if (!options.dryRun) {
1103
+ await Promise.all(deleted.map((event) => options.store.remove(event.id)));
1104
+ }
1105
+ return {
1106
+ deleted,
1107
+ deletedCount: deleted.length,
1108
+ dryRun: Boolean(options.dryRun),
1109
+ scannedCount: events.length
1110
+ };
1111
+ };
1112
+ var sleep = async (delayMs) => {
1113
+ if (delayMs <= 0) {
1114
+ return;
1115
+ }
1116
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1117
+ };
1118
+ var toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
1119
+ var signVoiceTraceSinkBody = async (input) => {
1120
+ const encoder = new TextEncoder;
1121
+ const key = await crypto.subtle.importKey("raw", encoder.encode(input.secret), {
1122
+ hash: "SHA-256",
1123
+ name: "HMAC"
1124
+ }, false, ["sign"]);
1125
+ const payload = encoder.encode(`${input.timestamp}.${input.body}`);
1126
+ const signature = await crypto.subtle.sign("HMAC", key, payload);
1127
+ return `sha256=${toHex(new Uint8Array(signature))}`;
1128
+ };
1129
+ var createVoiceTraceSinkDeliveryError = (input) => {
1130
+ if (input.response) {
1131
+ const statusText = input.response.statusText?.trim();
1132
+ return `Attempt ${input.attempt} failed with trace sink response ${input.response.status}${statusText ? ` ${statusText}` : ""}.`;
1133
+ }
1134
+ if (input.error instanceof Error) {
1135
+ return `Attempt ${input.attempt} failed: ${input.error.message}`;
1136
+ }
1137
+ return `Attempt ${input.attempt} failed: ${String(input.error)}`;
1138
+ };
1139
+ var normalizeVoiceTraceS3KeyPrefix = (prefix) => prefix?.trim().replace(/^\/+|\/+$/g, "") ?? "voice/trace-deliveries";
1140
+ var createVoiceTraceS3ObjectKey = (prefix, events) => {
1141
+ const firstEvent = events[0];
1142
+ const safeSessionId = encodeURIComponent(firstEvent?.sessionId ?? "trace");
1143
+ const safeEventId = encodeURIComponent(firstEvent?.id ?? crypto.randomUUID());
1144
+ return `${prefix}/${safeSessionId}/${Date.now()}-${safeEventId}.json`;
1145
+ };
1146
+ var resolveVoiceS3DeliveredTo = (options, key) => {
1147
+ const bucket = options.bucket;
1148
+ return bucket ? `s3://${bucket}/${key}` : `s3://${key}`;
1149
+ };
1150
+ var aggregateVoiceTraceSinkDeliveryStatus = (deliveries) => {
1151
+ const statuses = Object.values(deliveries).map((delivery) => delivery.status);
1152
+ if (statuses.length === 0 || statuses.every((status) => status === "skipped")) {
1153
+ return "skipped";
1154
+ }
1155
+ if (statuses.some((status) => status === "failed")) {
1156
+ return "failed";
1157
+ }
1158
+ return "delivered";
1159
+ };
1160
+ var createVoiceTraceHTTPSink = (options) => ({
1161
+ deliver: async ({ events }) => {
1162
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1163
+ if (typeof fetchImpl !== "function") {
1164
+ return {
1165
+ attempts: 0,
1166
+ deliveredTo: options.url,
1167
+ error: "Trace sink delivery failed: fetch is not available in this runtime.",
1168
+ eventCount: events.length,
1169
+ status: "failed"
1170
+ };
1171
+ }
1172
+ const maxRetries = Math.max(0, options.retries ?? 0);
1173
+ const backoffMs = Math.max(0, options.backoffMs ?? 250);
1174
+ const timeoutMs = Math.max(0, options.timeoutMs ?? 1e4);
1175
+ const payload = options.body ? await options.body({ events }) : {
1176
+ eventCount: events.length,
1177
+ events,
1178
+ source: "absolutejs-voice"
1179
+ };
1180
+ const body = JSON.stringify(payload);
1181
+ let lastError = "Trace sink delivery failed.";
1182
+ for (let attempt = 1;attempt <= maxRetries + 1; attempt += 1) {
1183
+ let controller;
1184
+ let timeout;
1185
+ try {
1186
+ const headers = {
1187
+ "content-type": "application/json",
1188
+ ...options.headers
1189
+ };
1190
+ if (options.signingSecret) {
1191
+ const timestamp = String(Date.now());
1192
+ headers["x-absolutejs-timestamp"] = timestamp;
1193
+ headers["x-absolutejs-signature"] = await signVoiceTraceSinkBody({
1194
+ body,
1195
+ secret: options.signingSecret,
1196
+ timestamp
1197
+ });
1198
+ }
1199
+ controller = timeoutMs > 0 ? new AbortController : undefined;
1200
+ if (controller && timeoutMs > 0) {
1201
+ timeout = setTimeout(() => controller?.abort(), timeoutMs);
1202
+ }
1203
+ const response = await fetchImpl(options.url, {
1204
+ body,
1205
+ headers,
1206
+ method: options.method ?? "POST",
1207
+ signal: controller?.signal
1208
+ });
1209
+ if (response.ok) {
1210
+ let responseBody;
1211
+ try {
1212
+ responseBody = await response.clone().json();
1213
+ } catch {
1214
+ responseBody = undefined;
1215
+ }
1216
+ return {
1217
+ attempts: attempt,
1218
+ deliveredAt: Date.now(),
1219
+ deliveredTo: options.url,
1220
+ eventCount: events.length,
1221
+ responseBody,
1222
+ status: "delivered"
1223
+ };
1224
+ }
1225
+ lastError = createVoiceTraceSinkDeliveryError({
1226
+ attempt,
1227
+ response
1228
+ });
1229
+ } catch (error) {
1230
+ lastError = createVoiceTraceSinkDeliveryError({
1231
+ attempt,
1232
+ error
1233
+ });
1234
+ } finally {
1235
+ if (timeout) {
1236
+ clearTimeout(timeout);
1237
+ }
1238
+ }
1239
+ if (attempt <= maxRetries) {
1240
+ await sleep(backoffMs * attempt);
1241
+ }
1242
+ }
1243
+ return {
1244
+ attempts: maxRetries + 1,
1245
+ deliveredTo: options.url,
1246
+ error: lastError,
1247
+ eventCount: events.length,
1248
+ status: "failed"
1249
+ };
1250
+ },
1251
+ eventTypes: options.eventTypes,
1252
+ id: options.id,
1253
+ kind: options.kind ?? "http"
1254
+ });
1255
+ var createVoiceTraceS3Sink = (options) => {
1256
+ const client = options.client ?? new Bun.S3Client(options);
1257
+ const keyPrefix = normalizeVoiceTraceS3KeyPrefix(options.keyPrefix);
1258
+ return {
1259
+ deliver: async ({ events }) => {
1260
+ const key = createVoiceTraceS3ObjectKey(keyPrefix, events);
1261
+ const payload = options.body ? await options.body({ events, key }) : {
1262
+ eventCount: events.length,
1263
+ events,
1264
+ key,
1265
+ source: "absolutejs-voice"
1266
+ };
1267
+ try {
1268
+ const file = client.file(key, options);
1269
+ await file.write(JSON.stringify(payload), {
1270
+ type: options.contentType ?? "application/json"
1271
+ });
1272
+ return {
1273
+ attempts: 1,
1274
+ deliveredAt: Date.now(),
1275
+ deliveredTo: resolveVoiceS3DeliveredTo(options, key),
1276
+ eventCount: events.length,
1277
+ responseBody: { key },
1278
+ status: "delivered"
1279
+ };
1280
+ } catch (error) {
1281
+ return {
1282
+ attempts: 1,
1283
+ deliveredTo: resolveVoiceS3DeliveredTo(options, key),
1284
+ error: error instanceof Error ? error.message : String(error),
1285
+ eventCount: events.length,
1286
+ status: "failed"
1287
+ };
1288
+ }
1289
+ },
1290
+ eventTypes: options.eventTypes,
1291
+ id: options.id,
1292
+ kind: options.kind ?? "s3"
1293
+ };
1294
+ };
1295
+ var deliverVoiceTraceEventsToSinks = async (input) => {
1296
+ const events = input.redact ? redactVoiceTraceEvents(input.events, input.redact) : input.events;
1297
+ const sinkDeliveries = {};
1298
+ for (const sink of input.sinks) {
1299
+ const sinkEvents = sink.eventTypes?.length ? events.filter((event) => sink.eventTypes?.includes(event.type)) : events;
1300
+ if (sinkEvents.length === 0) {
1301
+ sinkDeliveries[sink.id] = {
1302
+ attempts: 0,
1303
+ eventCount: 0,
1304
+ status: "skipped"
1305
+ };
1306
+ continue;
1307
+ }
1308
+ try {
1309
+ sinkDeliveries[sink.id] = await sink.deliver({
1310
+ events: sinkEvents
1311
+ });
1312
+ } catch (error) {
1313
+ sinkDeliveries[sink.id] = {
1314
+ attempts: 1,
1315
+ error: error instanceof Error ? error.message : String(error),
1316
+ eventCount: sinkEvents.length,
1317
+ status: "failed"
1318
+ };
1319
+ }
1320
+ }
1321
+ return {
1322
+ deliveredAt: Date.now(),
1323
+ eventCount: events.length,
1324
+ sinkDeliveries,
1325
+ status: aggregateVoiceTraceSinkDeliveryStatus(sinkDeliveries)
1326
+ };
1327
+ };
1328
+ var createVoiceTraceSinkStore = (options) => {
1329
+ const deliver = async (event) => {
1330
+ const result = await deliverVoiceTraceEventsToSinks({
1331
+ events: [event],
1332
+ redact: options.redact,
1333
+ sinks: options.sinks
1334
+ });
1335
+ await options.onDelivery?.(result);
1336
+ };
1337
+ return {
1338
+ append: async (event) => {
1339
+ const stored = await options.store.append(event);
1340
+ if (options.deliveryQueue) {
1341
+ const delivery2 = createVoiceTraceSinkDeliveryRecord({
1342
+ events: [stored]
1343
+ });
1344
+ await options.deliveryQueue.set(delivery2.id, delivery2);
1345
+ return stored;
1346
+ }
1347
+ const delivery = deliver(stored);
1348
+ if (options.awaitDelivery) {
1349
+ await delivery;
1350
+ } else {
1351
+ delivery.catch((error) => {
1352
+ options.onError?.(error);
1353
+ });
1354
+ }
1355
+ return stored;
1356
+ },
1357
+ get: (id) => options.store.get(id),
1358
+ list: (filter) => options.store.list(filter),
1359
+ remove: (id) => options.store.remove(id)
1360
+ };
1361
+ };
1362
+ var createVoiceMemoryTraceSinkDeliveryStore = () => {
1363
+ const deliveries = new Map;
1364
+ return {
1365
+ get: async (id) => deliveries.get(id),
1366
+ list: async () => [...deliveries.values()].sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id)),
1367
+ remove: async (id) => {
1368
+ deliveries.delete(id);
1369
+ },
1370
+ set: async (id, delivery) => {
1371
+ deliveries.set(id, delivery);
1372
+ }
1373
+ };
1374
+ };
1375
+ var createVoiceMemoryTraceEventStore = () => {
1376
+ const events = new Map;
1377
+ const append = async (event) => {
1378
+ const stored = createVoiceTraceEvent(event);
1379
+ events.set(stored.id, stored);
1380
+ return stored;
1381
+ };
1382
+ const get = async (id) => events.get(id);
1383
+ const list = async (filter) => filterVoiceTraceEvents([...events.values()], filter);
1384
+ const remove = async (id) => {
1385
+ events.delete(id);
1386
+ };
1387
+ return { append, get, list, remove };
1388
+ };
1389
+ var exportVoiceTrace = async (input) => {
1390
+ const events = await input.store.list(input.filter);
1391
+ return {
1392
+ exportedAt: Date.now(),
1393
+ events: input.redact ? redactVoiceTraceEvents(events, input.redact) : events,
1394
+ filter: input.filter,
1395
+ redacted: Boolean(input.redact)
1396
+ };
1397
+ };
1398
+ var toNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
1399
+ var escapeHtml3 = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1400
+ var formatTraceValue = (value) => {
1401
+ if (value === undefined || value === null) {
1402
+ return "";
1403
+ }
1404
+ if (typeof value === "string") {
1405
+ return value;
1406
+ }
1407
+ if (typeof value === "number" || typeof value === "boolean") {
1408
+ return String(value);
1409
+ }
1410
+ try {
1411
+ return JSON.stringify(value);
1412
+ } catch {
1413
+ return String(value);
1414
+ }
1415
+ };
1416
+ var DEFAULT_REDACTION_KEYS = [
1417
+ "apiKey",
1418
+ "authorization",
1419
+ "creditCard",
1420
+ "email",
1421
+ "externalId",
1422
+ "password",
1423
+ "phone",
1424
+ "secret",
1425
+ "ssn",
1426
+ "token"
1427
+ ];
1428
+ var DEFAULT_REDACTION_TEXT_KEYS = [
1429
+ "assistantText",
1430
+ "content",
1431
+ "error",
1432
+ "reason",
1433
+ "summary",
1434
+ "text"
1435
+ ];
1436
+ var EMAIL_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
1437
+ var PHONE_PATTERN = /(?<!\d)(?:\+?1[\s.-]?)?(?:\(?\d{3}\)?[\s.-]?)\d{3}[\s.-]?\d{4}(?!\d)/g;
1438
+ var normalizeRedactionKey = (key) => key.trim().toLowerCase().replace(/[^a-z0-9]/g, "");
1439
+ var resolveVoiceTraceRedactionOptions = (options = {}) => ({
1440
+ keys: typeof options === "boolean" ? DEFAULT_REDACTION_KEYS : options.keys ?? DEFAULT_REDACTION_KEYS,
1441
+ redactEmails: typeof options === "boolean" ? true : options.redactEmails ?? true,
1442
+ redactPhoneNumbers: typeof options === "boolean" ? true : options.redactPhoneNumbers ?? true,
1443
+ redactText: typeof options === "boolean" ? true : options.redactText ?? true,
1444
+ replacement: typeof options === "boolean" ? "[redacted]" : options.replacement ?? "[redacted]",
1445
+ textKeys: typeof options === "boolean" ? DEFAULT_REDACTION_TEXT_KEYS : options.textKeys ?? DEFAULT_REDACTION_TEXT_KEYS
1446
+ });
1447
+ var resolveReplacement = (input) => typeof input.options.replacement === "function" ? input.options.replacement({
1448
+ key: input.key,
1449
+ path: input.path,
1450
+ value: input.value
1451
+ }) : input.options.replacement;
1452
+ var redactVoiceTraceText = (value, options = {}, input = {}) => {
1453
+ const resolved = resolveVoiceTraceRedactionOptions(options);
1454
+ let redacted = value;
1455
+ const replacement = resolveReplacement({
1456
+ key: input.key,
1457
+ options: resolved,
1458
+ path: input.path ?? [],
1459
+ value
1460
+ });
1461
+ if (resolved.redactEmails) {
1462
+ redacted = redacted.replace(EMAIL_PATTERN, replacement);
1463
+ }
1464
+ if (resolved.redactPhoneNumbers) {
1465
+ redacted = redacted.replace(PHONE_PATTERN, replacement);
1466
+ }
1467
+ return redacted;
1468
+ };
1469
+ var redactTraceValue = (value, options, path) => {
1470
+ const key = path.at(-1);
1471
+ const normalizedKey = key ? normalizeRedactionKey(key) : undefined;
1472
+ const sensitiveKeys = new Set(options.keys.map(normalizeRedactionKey));
1473
+ const textKeys = new Set(options.textKeys.map(normalizeRedactionKey));
1474
+ if (normalizedKey && sensitiveKeys.has(normalizedKey) && (value === null || ["boolean", "number", "string", "undefined"].includes(typeof value))) {
1475
+ return resolveReplacement({
1476
+ key,
1477
+ options,
1478
+ path,
1479
+ value: String(value ?? "")
1480
+ });
1481
+ }
1482
+ if (typeof value === "string") {
1483
+ const shouldRedactText = options.redactText && (!normalizedKey || textKeys.has(normalizedKey) || path.length === 0);
1484
+ return shouldRedactText ? redactVoiceTraceText(value, options, {
1485
+ key,
1486
+ path
1487
+ }) : value;
1488
+ }
1489
+ if (Array.isArray(value)) {
1490
+ return value.map((item, index) => redactTraceValue(item, options, [...path, String(index)]));
1491
+ }
1492
+ if (typeof value === "object" && value) {
1493
+ return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [
1494
+ entryKey,
1495
+ redactTraceValue(entryValue, options, [...path, entryKey])
1496
+ ]));
1497
+ }
1498
+ return value;
1499
+ };
1500
+ var redactVoiceTraceEvent = (event, options = {}) => {
1501
+ const resolved = resolveVoiceTraceRedactionOptions(options);
1502
+ return {
1503
+ ...event,
1504
+ metadata: redactTraceValue(event.metadata, resolved, ["metadata"]),
1505
+ payload: redactTraceValue(event.payload, resolved, ["payload"])
1506
+ };
1507
+ };
1508
+ var redactVoiceTraceEvents = (events, options = {}) => events.map((event) => redactVoiceTraceEvent(event, options));
1509
+ var summarizeVoiceTrace = (events) => {
1510
+ const sorted = filterVoiceTraceEvents(events);
1511
+ const firstEvent = sorted[0];
1512
+ const lastEvent = sorted.at(-1);
1513
+ const lifecycleEvents = sorted.filter((event) => event.type === "call.lifecycle");
1514
+ const startEvent = lifecycleEvents.find((event) => event.payload.type === "start");
1515
+ const endEvent = lifecycleEvents.toReversed().find((event) => event.payload.type === "end");
1516
+ const costEvents = sorted.filter((event) => event.type === "turn.cost");
1517
+ const toolEvents = sorted.filter((event) => event.type === "agent.tool");
1518
+ const startedAt = startEvent?.at ?? firstEvent?.at;
1519
+ const endedAt = endEvent?.at ?? lastEvent?.at;
1520
+ const failed = sorted.some((event) => event.type === "session.error") || endEvent?.payload.disposition === "failed";
1521
+ return {
1522
+ assistantReplyCount: sorted.filter((event) => event.type === "turn.assistant").length,
1523
+ callDurationMs: startedAt !== undefined && endedAt !== undefined ? Math.max(0, endedAt - startedAt) : undefined,
1524
+ cost: {
1525
+ estimatedRelativeCostUnits: costEvents.reduce((total, event) => total + toNumber(event.payload.estimatedRelativeCostUnits), 0),
1526
+ totalBillableAudioMs: costEvents.reduce((total, event) => total + toNumber(event.payload.totalBillableAudioMs), 0)
1527
+ },
1528
+ endedAt,
1529
+ errorCount: sorted.filter((event) => event.type === "session.error").length,
1530
+ eventCount: sorted.length,
1531
+ failed,
1532
+ handoffCount: sorted.filter((event) => event.type === "agent.handoff").length,
1533
+ modelCallCount: sorted.filter((event) => event.type === "agent.model").length,
1534
+ sessionId: firstEvent?.sessionId,
1535
+ startedAt,
1536
+ toolCallCount: toolEvents.length,
1537
+ toolErrorCount: toolEvents.filter((event) => event.payload.status === "error").length,
1538
+ traceId: firstEvent?.traceId,
1539
+ transcriptCount: sorted.filter((event) => event.type === "turn.transcript").length,
1540
+ turnCount: sorted.filter((event) => event.type === "turn.committed").length
1541
+ };
1542
+ };
1543
+ var evaluateVoiceTrace = (events, options = {}) => {
1544
+ const summary = summarizeVoiceTrace(events);
1545
+ const issues = [];
1546
+ const maxHandoffs = options.maxHandoffs ?? 3;
1547
+ const maxToolErrors = options.maxToolErrors ?? 0;
1548
+ const maxModelCallsPerTurn = options.maxModelCallsPerTurn ?? 6;
1549
+ const turnCountForRatio = Math.max(1, summary.turnCount);
1550
+ if (options.requireCompletedCall !== false && !summary.endedAt) {
1551
+ issues.push({
1552
+ code: "call-not-ended",
1553
+ message: "Trace does not include a call end lifecycle event.",
1554
+ severity: "warning"
1555
+ });
1556
+ }
1557
+ if (summary.failed) {
1558
+ issues.push({
1559
+ code: "session-error",
1560
+ message: "Trace contains a session error or failed call disposition.",
1561
+ severity: "error"
1562
+ });
1563
+ }
1564
+ if (options.requireTranscript !== false && summary.transcriptCount === 0) {
1565
+ issues.push({
1566
+ code: "missing-transcript",
1567
+ message: "Trace does not include any transcript events.",
1568
+ severity: "error"
1569
+ });
1570
+ }
1571
+ if (options.requireTurn !== false && summary.turnCount === 0) {
1572
+ issues.push({
1573
+ code: "missing-turn",
1574
+ message: "Trace does not include any committed turns.",
1575
+ severity: "error"
1576
+ });
1577
+ }
1578
+ if (options.requireAssistantReply !== false && summary.turnCount > 0 && summary.assistantReplyCount === 0) {
1579
+ issues.push({
1580
+ code: "missing-assistant-reply",
1581
+ message: "Trace has committed turns but no assistant replies.",
1582
+ severity: "warning"
1583
+ });
1584
+ }
1585
+ if (summary.toolErrorCount > maxToolErrors) {
1586
+ issues.push({
1587
+ code: "tool-errors",
1588
+ message: `Trace has ${summary.toolErrorCount} tool error(s), above the allowed ${maxToolErrors}.`,
1589
+ severity: "error"
1590
+ });
1591
+ }
1592
+ if (summary.handoffCount > maxHandoffs) {
1593
+ issues.push({
1594
+ code: "too-many-handoffs",
1595
+ message: `Trace has ${summary.handoffCount} handoff(s), above the allowed ${maxHandoffs}.`,
1596
+ severity: "warning"
1597
+ });
1598
+ }
1599
+ if (summary.modelCallCount / turnCountForRatio > maxModelCallsPerTurn) {
1600
+ issues.push({
1601
+ code: "too-many-model-calls",
1602
+ message: `Trace averages more than ${maxModelCallsPerTurn} model calls per committed turn.`,
1603
+ severity: "warning"
1604
+ });
1605
+ }
1606
+ return {
1607
+ issues,
1608
+ pass: !issues.some((issue) => issue.severity === "error"),
1609
+ summary
1610
+ };
1611
+ };
1612
+ var renderTraceEventMarkdown = (event, startedAt) => {
1613
+ const offset = startedAt === undefined ? `${event.at}` : `+${Math.max(0, event.at - startedAt)}ms`;
1614
+ const label = `- ${offset} [${event.type}]`;
1615
+ switch (event.type) {
1616
+ case "turn.transcript":
1617
+ return `${label} ${event.payload.isFinal ? "final" : "partial"} "${formatTraceValue(event.payload.text)}"`;
1618
+ case "turn.committed":
1619
+ return `${label} committed "${formatTraceValue(event.payload.text)}"`;
1620
+ case "turn.assistant":
1621
+ return event.payload.text ? `${label} assistant "${formatTraceValue(event.payload.text)}"` : `${label} ${formatTraceValue(event.payload.status)}`;
1622
+ case "agent.tool":
1623
+ return `${label} ${formatTraceValue(event.payload.toolName)} ${formatTraceValue(event.payload.status)}`;
1624
+ case "agent.handoff":
1625
+ return `${label} ${formatTraceValue(event.payload.fromAgentId)} -> ${formatTraceValue(event.payload.targetAgentId)}`;
1626
+ case "session.error":
1627
+ return `${label} ${formatTraceValue(event.payload.error)}`;
1628
+ case "call.lifecycle":
1629
+ return `${label} ${formatTraceValue(event.payload.type)} ${formatTraceValue(event.payload.disposition)}`.trim();
1630
+ default:
1631
+ return `${label} ${formatTraceValue(event.payload)}`;
1632
+ }
1633
+ };
1634
+ var renderVoiceTraceMarkdown = (events, options = {}) => {
1635
+ const sorted = filterVoiceTraceEvents(options.redact ? redactVoiceTraceEvents(events, options.redact) : events);
1636
+ const summary = summarizeVoiceTrace(sorted);
1637
+ const evaluation = evaluateVoiceTrace(sorted, options.evaluation);
1638
+ const lines = [
1639
+ `# ${options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim()}`,
1640
+ "",
1641
+ `Pass: ${evaluation.pass ? "yes" : "no"}`,
1642
+ `Session: ${summary.sessionId ?? "unknown"}`,
1643
+ `Events: ${summary.eventCount}`,
1644
+ `Turns: ${summary.turnCount}`,
1645
+ `Transcripts: ${summary.transcriptCount}`,
1646
+ `Assistant replies: ${summary.assistantReplyCount}`,
1647
+ `Model calls: ${summary.modelCallCount}`,
1648
+ `Tool calls: ${summary.toolCallCount}`,
1649
+ `Handoffs: ${summary.handoffCount}`,
1650
+ `Errors: ${summary.errorCount}`,
1651
+ `Estimated cost units: ${summary.cost.estimatedRelativeCostUnits}`,
1652
+ ""
1653
+ ];
1654
+ if (evaluation.issues.length > 0) {
1655
+ lines.push("## Issues", "");
1656
+ for (const issue of evaluation.issues) {
1657
+ lines.push(`- [${issue.severity}] ${issue.code}: ${issue.message}`);
1658
+ }
1659
+ lines.push("");
1660
+ }
1661
+ lines.push("## Timeline", "");
1662
+ for (const event of sorted) {
1663
+ lines.push(renderTraceEventMarkdown(event, summary.startedAt));
1664
+ }
1665
+ return lines.join(`
1666
+ `);
1667
+ };
1668
+ var renderVoiceTraceHTML = (events, options = {}) => {
1669
+ const markdown = renderVoiceTraceMarkdown(events, options);
1670
+ const renderEvents = options.redact ? redactVoiceTraceEvents(events, options.redact) : events;
1671
+ const summary = summarizeVoiceTrace(renderEvents);
1672
+ const evaluation = evaluateVoiceTrace(renderEvents, options.evaluation);
1673
+ const eventRows = filterVoiceTraceEvents(renderEvents).map((event) => {
1674
+ const offset = summary.startedAt === undefined ? event.at : Math.max(0, event.at - summary.startedAt);
1675
+ return [
1676
+ "<tr>",
1677
+ `<td>${escapeHtml3(String(offset))}</td>`,
1678
+ `<td>${escapeHtml3(event.type)}</td>`,
1679
+ `<td>${escapeHtml3(event.turnId ?? "")}</td>`,
1680
+ `<td><code>${escapeHtml3(JSON.stringify(event.payload))}</code></td>`,
1681
+ "</tr>"
1682
+ ].join("");
1683
+ }).join(`
1684
+ `);
1685
+ return [
1686
+ "<!doctype html>",
1687
+ '<html lang="en">',
1688
+ "<head>",
1689
+ '<meta charset="utf-8" />',
1690
+ '<meta name="viewport" content="width=device-width, initial-scale=1" />',
1691
+ `<title>${escapeHtml3(options.title ?? "Voice Trace")}</title>`,
1692
+ "<style>",
1693
+ "body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;line-height:1.45;background:#f8f7f2;color:#181713}",
1694
+ "main{max-width:1100px;margin:auto}",
1695
+ ".summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:.75rem;margin:1rem 0}",
1696
+ ".card{background:white;border:1px solid #ded9cc;border-radius:12px;padding:1rem}",
1697
+ ".pass{color:#126b3a}.fail{color:#9d2222}",
1698
+ "table{border-collapse:collapse;width:100%;background:white;border:1px solid #ded9cc}",
1699
+ "th,td{border-bottom:1px solid #eee8dc;padding:.65rem;text-align:left;vertical-align:top}",
1700
+ "code{white-space:pre-wrap;word-break:break-word}",
1701
+ "pre{background:#181713;color:#f8f7f2;padding:1rem;border-radius:12px;overflow:auto}",
1702
+ "</style>",
1703
+ "</head>",
1704
+ "<body><main>",
1705
+ `<h1>${escapeHtml3(options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim())}</h1>`,
1706
+ `<p class="${evaluation.pass ? "pass" : "fail"}">QA: ${evaluation.pass ? "pass" : "fail"}</p>`,
1707
+ '<section class="summary">',
1708
+ `<div class="card"><strong>Events</strong><br>${summary.eventCount}</div>`,
1709
+ `<div class="card"><strong>Turns</strong><br>${summary.turnCount}</div>`,
1710
+ `<div class="card"><strong>Transcripts</strong><br>${summary.transcriptCount}</div>`,
1711
+ `<div class="card"><strong>Tool errors</strong><br>${summary.toolErrorCount}</div>`,
1712
+ `<div class="card"><strong>Cost units</strong><br>${summary.cost.estimatedRelativeCostUnits}</div>`,
1713
+ "</section>",
1714
+ "<h2>Timeline</h2>",
1715
+ "<table><thead><tr><th>Offset ms</th><th>Type</th><th>Turn</th><th>Payload</th></tr></thead><tbody>",
1716
+ eventRows,
1717
+ "</tbody></table>",
1718
+ "<h2>Markdown Export</h2>",
1719
+ `<pre>${escapeHtml3(markdown)}</pre>`,
1720
+ "</main></body></html>"
1721
+ ].join(`
1722
+ `);
1723
+ };
1724
+ var buildVoiceTraceReplay = (events, options = {}) => ({
1725
+ evaluation: evaluateVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events, options.evaluation),
1726
+ html: renderVoiceTraceHTML(events, options),
1727
+ markdown: renderVoiceTraceMarkdown(events, options),
1728
+ summary: summarizeVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events)
1729
+ });
1730
+
1731
+ // src/liveOps.ts
1732
+ var VOICE_LIVE_OPS_ACTIONS = [
1733
+ "assign",
1734
+ "create-task",
1735
+ "escalate",
1736
+ "force-handoff",
1737
+ "inject-instruction",
1738
+ "operator-takeover",
1739
+ "pause-assistant",
1740
+ "resume-assistant",
1741
+ "tag"
1742
+ ];
1743
+ var isVoiceLiveOpsAction = (value) => typeof value === "string" && VOICE_LIVE_OPS_ACTIONS.includes(value);
1744
+ var toStringValue = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
1745
+ var createVoiceMemoryLiveOpsControlStore = () => {
1746
+ const states = new Map;
1747
+ return {
1748
+ get: (sessionId) => states.get(sessionId),
1749
+ set: (sessionId, state) => {
1750
+ states.set(sessionId, state);
1751
+ }
1752
+ };
1753
+ };
1754
+ var getVoiceLiveOpsControlStatus = (action) => {
1755
+ switch (action) {
1756
+ case "force-handoff":
1757
+ return "handoff-forced";
1758
+ case "inject-instruction":
1759
+ return "instruction-injected";
1760
+ case "operator-takeover":
1761
+ return "operator-takeover";
1762
+ case "pause-assistant":
1763
+ return "assistant-paused";
1764
+ case "resume-assistant":
1765
+ return "assistant-resumed";
1766
+ default:
1767
+ return "recorded";
1768
+ }
1769
+ };
1770
+ var buildVoiceLiveOpsControlState = (input) => ({
1771
+ assistantPaused: input.action === "pause-assistant" || input.action === "operator-takeover" || input.action === "force-handoff" ? true : input.action === "resume-assistant" ? false : input.previous?.assistantPaused ?? false,
1772
+ handoffTarget: input.action === "force-handoff" ? input.tag : input.previous?.handoffTarget,
1773
+ injectedInstruction: input.action === "inject-instruction" ? input.detail : input.previous?.injectedInstruction,
1774
+ lastAction: input.action,
1775
+ lastUpdatedAt: input.at ?? Date.now(),
1776
+ operator: input.assignee,
1777
+ operatorTakeover: input.action === "operator-takeover" ? true : input.action === "resume-assistant" ? false : input.previous?.operatorTakeover ?? false,
1778
+ status: getVoiceLiveOpsControlStatus(input.action),
1779
+ tag: input.tag
1780
+ });
1781
+ var createVoiceLiveOpsController = (options = {}) => {
1782
+ const store = options.store ?? createVoiceMemoryLiveOpsControlStore();
1783
+ const perform = async (input) => {
1784
+ if (!input.sessionId) {
1785
+ throw new Error("Voice live ops action requires sessionId.");
1786
+ }
1787
+ if (!isVoiceLiveOpsAction(input.action)) {
1788
+ throw new Error("Voice live ops action is not supported.");
1789
+ }
1790
+ const at = Date.now();
1791
+ const assignee = input.assignee ?? options.defaultAssignee ?? "operator";
1792
+ const tag = input.tag ?? options.defaultTag ?? "live-ops";
1793
+ const detail = input.detail ?? options.defaultDetail ?? input.action;
1794
+ const previous = await store.get(input.sessionId);
1795
+ const control = buildVoiceLiveOpsControlState({
1796
+ ...input,
1797
+ assignee,
1798
+ at,
1799
+ detail,
1800
+ previous,
1801
+ tag
1802
+ });
1803
+ await store.set(input.sessionId, control);
1804
+ const traceId = `voice-live-ops:${input.sessionId}:${input.action}:${at}`;
1805
+ await Promise.all([
1806
+ options.audit?.append(createVoiceAuditEvent({
1807
+ action: `voice.live_ops.${input.action}`,
1808
+ actor: {
1809
+ id: assignee,
1810
+ kind: "operator",
1811
+ name: assignee
1812
+ },
1813
+ at,
1814
+ metadata: {
1815
+ source: "voice-live-ops",
1816
+ tag
1817
+ },
1818
+ outcome: "success",
1819
+ payload: {
1820
+ action: input.action,
1821
+ assignee,
1822
+ control,
1823
+ detail,
1824
+ tag
1825
+ },
1826
+ resource: {
1827
+ id: input.sessionId,
1828
+ type: "voice.session"
1829
+ },
1830
+ sessionId: input.sessionId,
1831
+ traceId,
1832
+ type: "operator.action"
1833
+ })),
1834
+ options.trace?.append(createVoiceTraceEvent({
1835
+ at,
1836
+ metadata: {
1837
+ source: "voice-live-ops",
1838
+ tag
1839
+ },
1840
+ payload: {
1841
+ action: input.action,
1842
+ assignee,
1843
+ control,
1844
+ detail,
1845
+ status: "success",
1846
+ tag
1847
+ },
1848
+ sessionId: input.sessionId,
1849
+ traceId,
1850
+ type: "operator.action"
1851
+ }))
1852
+ ]);
1853
+ const result = {
1854
+ action: input.action,
1855
+ control,
1856
+ ok: true,
1857
+ sessionId: input.sessionId
1858
+ };
1859
+ await options.onAction?.({
1860
+ ...result,
1861
+ assignee,
1862
+ detail,
1863
+ tag
1864
+ });
1865
+ return result;
1866
+ };
1867
+ return {
1868
+ get: (sessionId) => store.get(sessionId),
1869
+ perform,
1870
+ store
1871
+ };
1872
+ };
1873
+ var readVoiceLiveOpsActionInput = async (request) => {
1874
+ const body = await request.json().catch(() => null);
1875
+ if (!body || typeof body !== "object") {
1876
+ throw new Error("Voice live ops action requires a JSON body.");
1877
+ }
1878
+ const record = body;
1879
+ const action = record.action;
1880
+ const sessionId = toStringValue(record.sessionId);
1881
+ if (!sessionId || !isVoiceLiveOpsAction(action)) {
1882
+ throw new Error("Voice live ops action requires valid sessionId and action.");
1883
+ }
1884
+ return {
1885
+ action,
1886
+ assignee: toStringValue(record.assignee),
1887
+ detail: toStringValue(record.detail),
1888
+ sessionId,
1889
+ tag: toStringValue(record.tag)
1890
+ };
1891
+ };
1892
+ var createVoiceLiveOpsRoutes = (options = {}) => {
1893
+ const controller = createVoiceLiveOpsController(options);
1894
+ const path = options.path ?? "/api/voice/live-ops/action";
1895
+ const controlPath = options.controlPath ?? "/api/voice/live-ops/control/:sessionId";
1896
+ return new Elysia({
1897
+ name: options.name ?? "absolutejs-voice-live-ops"
1898
+ }).post(path, async ({ request, set }) => {
1899
+ try {
1900
+ return await controller.perform(await readVoiceLiveOpsActionInput(request));
1901
+ } catch (error) {
1902
+ set.status = 400;
1903
+ return {
1904
+ error: error instanceof Error ? error.message : String(error),
1905
+ ok: false
1906
+ };
1907
+ }
1908
+ }).get(controlPath, async ({ params }) => {
1909
+ const sessionId = params.sessionId;
1910
+ return {
1911
+ control: await controller.get(sessionId),
1912
+ ok: true,
1913
+ sessionId
1914
+ };
1915
+ });
1916
+ };
1917
+
1918
+ // src/client/liveOpsWidget.ts
1919
+ var ACTION_LABELS = {
1920
+ assign: "Assign",
1921
+ "create-task": "Create task",
1922
+ escalate: "Escalate",
1923
+ "force-handoff": "Force handoff",
1924
+ "inject-instruction": "Inject instruction",
1925
+ "operator-takeover": "Take over",
1926
+ "pause-assistant": "Pause assistant",
1927
+ "resume-assistant": "Resume assistant",
1928
+ tag: "Tag"
1929
+ };
1930
+ var escapeHtml4 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1931
+ var createVoiceLiveOpsInput = (action, input) => ({
1932
+ action,
1933
+ assignee: input.assignee,
1934
+ detail: input.detail,
1935
+ sessionId: input.sessionId ?? "",
1936
+ tag: input.tag
1937
+ });
1938
+ var renderVoiceLiveOpsHTML = (snapshot, options = {}) => {
1939
+ const sessionId = options.getSessionId?.() ?? "";
1940
+ const disabled = snapshot.isRunning || !sessionId;
1941
+ 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("");
1942
+ 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>';
1943
+ return `<section class="absolute-voice-live-ops">
1944
+ <header class="absolute-voice-live-ops__header">
1945
+ <span>${escapeHtml4(options.title ?? "Live Ops")}</span>
1946
+ <strong>${escapeHtml4(sessionId || "No active session")}</strong>
1947
+ </header>
1948
+ <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>
1949
+ <label><span>Operator</span><input data-absolute-voice-live-ops-assignee value="${escapeHtml4(options.defaultAssignee ?? "operator")}" /></label>
1950
+ <label><span>Tag / handoff target</span><input data-absolute-voice-live-ops-tag value="${escapeHtml4(options.defaultTag ?? "live-ops")}" /></label>
1951
+ <label><span>Detail / instruction</span><input data-absolute-voice-live-ops-detail value="${escapeHtml4(options.defaultDetail ?? "Operator marked this live session.")}" /></label>
1952
+ <div class="absolute-voice-live-ops__actions">${actions}</div>
1953
+ ${result}
1954
+ </section>`;
1955
+ };
1956
+ 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}`;
1957
+ var mountVoiceLiveOps = (element, options = {}) => {
1958
+ const store = createVoiceLiveOpsStore(options);
1959
+ let assignee = options.defaultAssignee ?? "operator";
1960
+ let detail = options.defaultDetail ?? "Operator marked this live session.";
1961
+ let tag = options.defaultTag ?? "live-ops";
1962
+ const syncInputs = () => {
1963
+ const assigneeInput = element.querySelector("[data-absolute-voice-live-ops-assignee]");
1964
+ const detailInput = element.querySelector("[data-absolute-voice-live-ops-detail]");
1965
+ const tagInput = element.querySelector("[data-absolute-voice-live-ops-tag]");
1966
+ if (assigneeInput instanceof HTMLInputElement) {
1967
+ assignee = assigneeInput.value;
1968
+ }
1969
+ if (detailInput instanceof HTMLInputElement) {
1970
+ detail = detailInput.value;
1971
+ }
1972
+ if (tagInput instanceof HTMLInputElement) {
1973
+ tag = tagInput.value;
1974
+ }
1975
+ };
1976
+ const render = () => {
1977
+ element.innerHTML = renderVoiceLiveOpsHTML(store.getSnapshot(), {
1978
+ ...options,
1979
+ defaultAssignee: assignee,
1980
+ defaultDetail: detail,
1981
+ defaultTag: tag
1982
+ });
1983
+ };
1984
+ const unsubscribe = store.subscribe(render);
1985
+ const handleInput = () => syncInputs();
1986
+ const handleClick = (event) => {
1987
+ const target = event.target;
1988
+ if (!(target instanceof Element)) {
1989
+ return;
1990
+ }
1991
+ const button = target.closest("[data-absolute-voice-live-ops-action]");
1992
+ const action = button?.getAttribute("data-absolute-voice-live-ops-action");
1993
+ if (!action) {
1994
+ return;
1995
+ }
1996
+ syncInputs();
1997
+ store.run(createVoiceLiveOpsInput(action, {
1998
+ assignee,
1999
+ detail,
2000
+ sessionId: options.getSessionId?.(),
2001
+ tag
2002
+ })).catch(() => {});
2003
+ };
2004
+ element.addEventListener?.("click", handleClick);
2005
+ element.addEventListener?.("input", handleInput);
2006
+ render();
2007
+ return {
2008
+ close: () => {
2009
+ element.removeEventListener?.("click", handleClick);
2010
+ element.removeEventListener?.("input", handleInput);
2011
+ unsubscribe();
2012
+ store.close();
2013
+ },
2014
+ run: store.run
2015
+ };
2016
+ };
2017
+ var defineVoiceLiveOpsElement = (tagName = "absolute-voice-live-ops", options = {}) => {
2018
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
2019
+ return;
2020
+ }
2021
+ customElements.define(tagName, class AbsoluteVoiceLiveOpsElement extends HTMLElement {
2022
+ mounted;
2023
+ connectedCallback() {
2024
+ this.mounted = mountVoiceLiveOps(this, {
2025
+ ...options,
2026
+ description: this.getAttribute("description") ?? options.description,
2027
+ getSessionId: options.getSessionId ?? (() => this.getAttribute("session-id") ?? undefined),
2028
+ title: this.getAttribute("title") ?? options.title
2029
+ });
2030
+ }
2031
+ disconnectedCallback() {
2032
+ this.mounted?.close();
2033
+ this.mounted = undefined;
2034
+ }
2035
+ });
2036
+ };
2037
+
2038
+ // src/svelte/createVoiceLiveOps.ts
2039
+ var createVoiceLiveOps = (options = {}) => {
2040
+ const store = createVoiceLiveOpsStore(options);
2041
+ return {
2042
+ close: store.close,
2043
+ getHTML: () => renderVoiceLiveOpsHTML(store.getSnapshot(), options),
2044
+ getSnapshot: store.getSnapshot,
2045
+ mount: (element) => mountVoiceLiveOps(element, options),
2046
+ run: store.run,
2047
+ subscribe: store.subscribe
2048
+ };
2049
+ };
763
2050
  // src/client/opsStatus.ts
764
2051
  var fetchVoiceOpsStatus = async (path = "/api/voice/ops-status", options = {}) => {
765
2052
  const fetchImpl = options.fetch ?? globalThis.fetch;
@@ -849,7 +2136,7 @@ var SURFACE_LABELS = {
849
2136
  sessions: "Sessions",
850
2137
  workflows: "Workflows"
851
2138
  };
852
- var escapeHtml3 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2139
+ var escapeHtml5 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
853
2140
  var readNumber = (value, key) => value && typeof value === "object" && (key in value) ? Number(value[key] ?? 0) : 0;
854
2141
  var surfaceDetail = (surface) => {
855
2142
  const total = readNumber(surface, "total");
@@ -904,24 +2191,24 @@ var createVoiceOpsStatusViewModel = (snapshot, options = {}) => {
904
2191
  };
905
2192
  var renderVoiceOpsStatusHTML = (snapshot, options = {}) => {
906
2193
  const model = createVoiceOpsStatusViewModel(snapshot, options);
907
- const surfaces = model.surfaces.length ? model.surfaces.map((surface) => `<li class="absolute-voice-ops-status__surface absolute-voice-ops-status__surface--${escapeHtml3(surface.status)}">
908
- <span>${escapeHtml3(surface.label)}</span>
909
- <strong>${escapeHtml3(surface.detail)}</strong>
2194
+ const surfaces = model.surfaces.length ? model.surfaces.map((surface) => `<li class="absolute-voice-ops-status__surface absolute-voice-ops-status__surface--${escapeHtml5(surface.status)}">
2195
+ <span>${escapeHtml5(surface.label)}</span>
2196
+ <strong>${escapeHtml5(surface.detail)}</strong>
910
2197
  </li>`).join("") : '<li class="absolute-voice-ops-status__surface"><span>Status</span><strong>Waiting for first check</strong></li>';
911
- const links = model.links.length ? `<nav class="absolute-voice-ops-status__links">${model.links.slice(0, 4).map((link) => `<a href="${escapeHtml3(link.href)}">${escapeHtml3(link.label)}</a>`).join("")}</nav>` : "";
912
- return `<section class="absolute-voice-ops-status absolute-voice-ops-status--${escapeHtml3(model.status)}">
2198
+ const links = model.links.length ? `<nav class="absolute-voice-ops-status__links">${model.links.slice(0, 4).map((link) => `<a href="${escapeHtml5(link.href)}">${escapeHtml5(link.label)}</a>`).join("")}</nav>` : "";
2199
+ return `<section class="absolute-voice-ops-status absolute-voice-ops-status--${escapeHtml5(model.status)}">
913
2200
  <header class="absolute-voice-ops-status__header">
914
- <span class="absolute-voice-ops-status__eyebrow">${escapeHtml3(model.title)}</span>
915
- <strong class="absolute-voice-ops-status__label">${escapeHtml3(model.label)}</strong>
2201
+ <span class="absolute-voice-ops-status__eyebrow">${escapeHtml5(model.title)}</span>
2202
+ <strong class="absolute-voice-ops-status__label">${escapeHtml5(model.label)}</strong>
916
2203
  </header>
917
- <p class="absolute-voice-ops-status__description">${escapeHtml3(model.description)}</p>
2204
+ <p class="absolute-voice-ops-status__description">${escapeHtml5(model.description)}</p>
918
2205
  <div class="absolute-voice-ops-status__summary">
919
2206
  <span>${model.passed} passing</span>
920
2207
  <span>${Math.max(model.total - model.passed, 0)} failing</span>
921
2208
  <span>${model.total} checks</span>
922
2209
  </div>
923
2210
  <ul class="absolute-voice-ops-status__surfaces">${surfaces}</ul>
924
- ${model.error ? `<p class="absolute-voice-ops-status__error">${escapeHtml3(model.error)}</p>` : ""}
2211
+ ${model.error ? `<p class="absolute-voice-ops-status__error">${escapeHtml5(model.error)}</p>` : ""}
925
2212
  ${links}
926
2213
  </section>`;
927
2214
  };
@@ -1056,7 +2343,7 @@ var createVoiceProviderSimulationControlsStore = (options) => {
1056
2343
  };
1057
2344
 
1058
2345
  // src/client/providerSimulationControlsWidget.ts
1059
- var escapeHtml4 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2346
+ var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1060
2347
  var formatKind = (kind) => (kind ?? "stt").toUpperCase();
1061
2348
  var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
1062
2349
  const configuredProviders = options.providers.filter((provider) => provider.configured !== false);
@@ -1076,18 +2363,18 @@ var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
1076
2363
  };
1077
2364
  var renderVoiceProviderSimulationControlsHTML = (snapshot, options) => {
1078
2365
  const model = createVoiceProviderSimulationControlsViewModel(snapshot, options);
1079
- const failureButtons = model.failureProviders.map((provider) => `<button type="button" data-voice-provider-fail="${escapeHtml4(provider.provider)}"${!model.canSimulateFailure || snapshot.isRunning ? " disabled" : ""}>Simulate ${escapeHtml4(provider.provider)} ${escapeHtml4(formatKind(options.kind))} failure</button>`).join("");
1080
- const recoveryButtons = model.providers.map((provider) => `<button type="button" data-voice-provider-recover="${escapeHtml4(provider.provider)}"${snapshot.isRunning ? " disabled" : ""}>Mark ${escapeHtml4(provider.provider)} recovered</button>`).join("");
2366
+ 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("");
2367
+ 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("");
1081
2368
  return `<section class="absolute-voice-provider-simulation absolute-voice-provider-simulation--${snapshot.error ? "error" : snapshot.isRunning ? "running" : "ready"}">
1082
2369
  <header class="absolute-voice-provider-simulation__header">
1083
- <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml4(model.title)}</span>
1084
- <strong class="absolute-voice-provider-simulation__label">${escapeHtml4(model.label)}</strong>
2370
+ <span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml6(model.title)}</span>
2371
+ <strong class="absolute-voice-provider-simulation__label">${escapeHtml6(model.label)}</strong>
1085
2372
  </header>
1086
- <p class="absolute-voice-provider-simulation__description">${escapeHtml4(model.description)}</p>
1087
- ${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml4(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
2373
+ <p class="absolute-voice-provider-simulation__description">${escapeHtml6(model.description)}</p>
2374
+ ${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml6(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
1088
2375
  <div class="absolute-voice-provider-simulation__actions">${failureButtons}${recoveryButtons}</div>
1089
- ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml4(snapshot.error)}</p>` : ""}
1090
- ${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml4(model.resultText)}</pre>` : ""}
2376
+ ${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml6(snapshot.error)}</p>` : ""}
2377
+ ${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml6(model.resultText)}</pre>` : ""}
1091
2378
  </section>`;
1092
2379
  };
1093
2380
  var bindVoiceProviderSimulationControls = (element, store) => {
@@ -1244,7 +2531,7 @@ var createVoiceProviderCapabilitiesStore = (path = "/api/provider-capabilities",
1244
2531
  // src/client/providerCapabilitiesWidget.ts
1245
2532
  var DEFAULT_TITLE4 = "Provider Capabilities";
1246
2533
  var DEFAULT_DESCRIPTION4 = "Configured, selected, and healthy voice providers for this deployment.";
1247
- var escapeHtml5 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2534
+ var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1248
2535
  var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
1249
2536
  var formatKind2 = (kind) => kind.toUpperCase();
1250
2537
  var formatStatus = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
@@ -1299,25 +2586,25 @@ var createVoiceProviderCapabilitiesViewModel = (snapshot, options = {}) => {
1299
2586
  };
1300
2587
  var renderVoiceProviderCapabilitiesHTML = (snapshot, options = {}) => {
1301
2588
  const model = createVoiceProviderCapabilitiesViewModel(snapshot, options);
1302
- const capabilities = model.capabilities.length ? `<div class="absolute-voice-provider-capabilities__providers">${model.capabilities.map((capability) => `<article class="absolute-voice-provider-capabilities__provider absolute-voice-provider-capabilities__provider--${escapeHtml5(capability.status)}">
2589
+ const capabilities = model.capabilities.length ? `<div class="absolute-voice-provider-capabilities__providers">${model.capabilities.map((capability) => `<article class="absolute-voice-provider-capabilities__provider absolute-voice-provider-capabilities__provider--${escapeHtml7(capability.status)}">
1303
2590
  <header>
1304
- <strong>${escapeHtml5(capability.label)}</strong>
1305
- <span>${escapeHtml5(formatStatus(capability.status))}</span>
2591
+ <strong>${escapeHtml7(capability.label)}</strong>
2592
+ <span>${escapeHtml7(formatStatus(capability.status))}</span>
1306
2593
  </header>
1307
- <p>${escapeHtml5(capability.detail)}</p>
2594
+ <p>${escapeHtml7(capability.detail)}</p>
1308
2595
  <dl>${capability.rows.map((row) => `<div>
1309
- <dt>${escapeHtml5(row.label)}</dt>
1310
- <dd>${escapeHtml5(row.value)}</dd>
2596
+ <dt>${escapeHtml7(row.label)}</dt>
2597
+ <dd>${escapeHtml7(row.value)}</dd>
1311
2598
  </div>`).join("")}</dl>
1312
2599
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-capabilities__empty">Configure provider capabilities to see deployment coverage.</p>';
1313
- return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml5(model.status)}">
2600
+ return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml7(model.status)}">
1314
2601
  <header class="absolute-voice-provider-capabilities__header">
1315
- <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml5(model.title)}</span>
1316
- <strong class="absolute-voice-provider-capabilities__label">${escapeHtml5(model.label)}</strong>
2602
+ <span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml7(model.title)}</span>
2603
+ <strong class="absolute-voice-provider-capabilities__label">${escapeHtml7(model.label)}</strong>
1317
2604
  </header>
1318
- <p class="absolute-voice-provider-capabilities__description">${escapeHtml5(model.description)}</p>
2605
+ <p class="absolute-voice-provider-capabilities__description">${escapeHtml7(model.description)}</p>
1319
2606
  ${capabilities}
1320
- ${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml5(model.error)}</p>` : ""}
2607
+ ${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml7(model.error)}</p>` : ""}
1321
2608
  </section>`;
1322
2609
  };
1323
2610
  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}`;
@@ -1445,7 +2732,7 @@ var createVoiceProviderContractsStore = (path = "/api/provider-contracts", optio
1445
2732
  // src/client/providerContractsWidget.ts
1446
2733
  var DEFAULT_TITLE5 = "Provider Contracts";
1447
2734
  var DEFAULT_DESCRIPTION5 = "Production contract coverage for provider env, latency, fallback, streaming, and capabilities.";
1448
- var escapeHtml6 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2735
+ var escapeHtml8 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1449
2736
  var formatProvider2 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
1450
2737
  var formatStatus2 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
1451
2738
  var contractDetail = (row) => {
@@ -1489,26 +2776,26 @@ var createVoiceProviderContractsViewModel = (snapshot, options = {}) => {
1489
2776
  };
1490
2777
  var renderVoiceProviderContractsHTML = (snapshot, options = {}) => {
1491
2778
  const model = createVoiceProviderContractsViewModel(snapshot, options);
1492
- const rows = model.rows.length ? `<div class="absolute-voice-provider-contracts__rows">${model.rows.map((row) => `<article class="absolute-voice-provider-contracts__row absolute-voice-provider-contracts__row--${escapeHtml6(row.status)}">
2779
+ const rows = model.rows.length ? `<div class="absolute-voice-provider-contracts__rows">${model.rows.map((row) => `<article class="absolute-voice-provider-contracts__row absolute-voice-provider-contracts__row--${escapeHtml8(row.status)}">
1493
2780
  <header>
1494
- <strong>${escapeHtml6(row.label)}</strong>
1495
- <span>${escapeHtml6(formatStatus2(row.status))}</span>
2781
+ <strong>${escapeHtml8(row.label)}</strong>
2782
+ <span>${escapeHtml8(formatStatus2(row.status))}</span>
1496
2783
  </header>
1497
- <p>${escapeHtml6(row.detail)}</p>
1498
- ${row.remediations.length ? `<ul class="absolute-voice-provider-contracts__remediations">${row.remediations.map((remediation) => `<li>${remediation.href ? `<a href="${escapeHtml6(remediation.href)}">${escapeHtml6(remediation.label)}</a>` : `<strong>${escapeHtml6(remediation.label)}</strong>`}<span>${escapeHtml6(remediation.detail)}</span></li>`).join("")}</ul>` : ""}
2784
+ <p>${escapeHtml8(row.detail)}</p>
2785
+ ${row.remediations.length ? `<ul class="absolute-voice-provider-contracts__remediations">${row.remediations.map((remediation) => `<li>${remediation.href ? `<a href="${escapeHtml8(remediation.href)}">${escapeHtml8(remediation.label)}</a>` : `<strong>${escapeHtml8(remediation.label)}</strong>`}<span>${escapeHtml8(remediation.detail)}</span></li>`).join("")}</ul>` : ""}
1499
2786
  <dl>${row.rows.map((item) => `<div>
1500
- <dt>${escapeHtml6(item.label)}</dt>
1501
- <dd>${escapeHtml6(item.value)}</dd>
2787
+ <dt>${escapeHtml8(item.label)}</dt>
2788
+ <dd>${escapeHtml8(item.value)}</dd>
1502
2789
  </div>`).join("")}</dl>
1503
2790
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-contracts__empty">Configure provider contracts to see production coverage.</p>';
1504
- return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${escapeHtml6(model.status)}">
2791
+ return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${escapeHtml8(model.status)}">
1505
2792
  <header class="absolute-voice-provider-contracts__header">
1506
- <span class="absolute-voice-provider-contracts__eyebrow">${escapeHtml6(model.title)}</span>
1507
- <strong class="absolute-voice-provider-contracts__label">${escapeHtml6(model.label)}</strong>
2793
+ <span class="absolute-voice-provider-contracts__eyebrow">${escapeHtml8(model.title)}</span>
2794
+ <strong class="absolute-voice-provider-contracts__label">${escapeHtml8(model.label)}</strong>
1508
2795
  </header>
1509
- <p class="absolute-voice-provider-contracts__description">${escapeHtml6(model.description)}</p>
2796
+ <p class="absolute-voice-provider-contracts__description">${escapeHtml8(model.description)}</p>
1510
2797
  ${rows}
1511
- ${model.error ? `<p class="absolute-voice-provider-contracts__error">${escapeHtml6(model.error)}</p>` : ""}
2798
+ ${model.error ? `<p class="absolute-voice-provider-contracts__error">${escapeHtml8(model.error)}</p>` : ""}
1512
2799
  </section>`;
1513
2800
  };
1514
2801
  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}`;
@@ -2283,7 +3570,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
2283
3570
  // src/client/providerStatusWidget.ts
2284
3571
  var DEFAULT_TITLE6 = "Voice Providers";
2285
3572
  var DEFAULT_DESCRIPTION6 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
2286
- var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3573
+ var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2287
3574
  var formatProvider3 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
2288
3575
  var formatStatus3 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
2289
3576
  var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
@@ -2339,25 +3626,25 @@ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
2339
3626
  };
2340
3627
  var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
2341
3628
  const model = createVoiceProviderStatusViewModel(snapshot, options);
2342
- 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)}">
3629
+ 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)}">
2343
3630
  <header>
2344
- <strong>${escapeHtml7(provider.label)}</strong>
2345
- <span>${escapeHtml7(formatStatus3(provider.status))}</span>
3631
+ <strong>${escapeHtml9(provider.label)}</strong>
3632
+ <span>${escapeHtml9(formatStatus3(provider.status))}</span>
2346
3633
  </header>
2347
- <p>${escapeHtml7(provider.detail)}</p>
3634
+ <p>${escapeHtml9(provider.detail)}</p>
2348
3635
  <dl>${provider.rows.map((row) => `<div>
2349
- <dt>${escapeHtml7(row.label)}</dt>
2350
- <dd>${escapeHtml7(row.value)}</dd>
3636
+ <dt>${escapeHtml9(row.label)}</dt>
3637
+ <dd>${escapeHtml9(row.value)}</dd>
2351
3638
  </div>`).join("")}</dl>
2352
3639
  </article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
2353
- return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml7(model.status)}">
3640
+ return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml9(model.status)}">
2354
3641
  <header class="absolute-voice-provider-status__header">
2355
- <span class="absolute-voice-provider-status__eyebrow">${escapeHtml7(model.title)}</span>
2356
- <strong class="absolute-voice-provider-status__label">${escapeHtml7(model.label)}</strong>
3642
+ <span class="absolute-voice-provider-status__eyebrow">${escapeHtml9(model.title)}</span>
3643
+ <strong class="absolute-voice-provider-status__label">${escapeHtml9(model.label)}</strong>
2357
3644
  </header>
2358
- <p class="absolute-voice-provider-status__description">${escapeHtml7(model.description)}</p>
3645
+ <p class="absolute-voice-provider-status__description">${escapeHtml9(model.description)}</p>
2359
3646
  ${providers}
2360
- ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml7(model.error)}</p>` : ""}
3647
+ ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml9(model.error)}</p>` : ""}
2361
3648
  </section>`;
2362
3649
  };
2363
3650
  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}`;
@@ -2490,7 +3777,7 @@ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {})
2490
3777
  // src/client/routingStatusWidget.ts
2491
3778
  var DEFAULT_TITLE7 = "Voice Routing";
2492
3779
  var DEFAULT_DESCRIPTION7 = "Latest provider routing decision from the self-hosted trace store.";
2493
- var escapeHtml8 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3780
+ var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2494
3781
  var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
2495
3782
  var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
2496
3783
  const decision = snapshot.decision;
@@ -2527,17 +3814,17 @@ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
2527
3814
  var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
2528
3815
  const model = createVoiceRoutingStatusViewModel(snapshot, options);
2529
3816
  const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
2530
- <span>${escapeHtml8(row.label)}</span>
2531
- <strong>${escapeHtml8(row.value)}</strong>
3817
+ <span>${escapeHtml10(row.label)}</span>
3818
+ <strong>${escapeHtml10(row.value)}</strong>
2532
3819
  </div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
2533
- return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml8(model.status)}">
3820
+ return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml10(model.status)}">
2534
3821
  <header class="absolute-voice-routing-status__header">
2535
- <span class="absolute-voice-routing-status__eyebrow">${escapeHtml8(model.title)}</span>
2536
- <strong class="absolute-voice-routing-status__label">${escapeHtml8(model.label)}</strong>
3822
+ <span class="absolute-voice-routing-status__eyebrow">${escapeHtml10(model.title)}</span>
3823
+ <strong class="absolute-voice-routing-status__label">${escapeHtml10(model.label)}</strong>
2537
3824
  </header>
2538
- <p class="absolute-voice-routing-status__description">${escapeHtml8(model.description)}</p>
3825
+ <p class="absolute-voice-routing-status__description">${escapeHtml10(model.description)}</p>
2539
3826
  ${rows}
2540
- ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml8(model.error)}</p>` : ""}
3827
+ ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml10(model.error)}</p>` : ""}
2541
3828
  </section>`;
2542
3829
  };
2543
3830
  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}`;
@@ -2670,7 +3957,7 @@ var createVoiceTraceTimelineStore = (path = "/api/voice-traces", options = {}) =
2670
3957
  // src/client/traceTimelineWidget.ts
2671
3958
  var DEFAULT_TITLE8 = "Voice Traces";
2672
3959
  var DEFAULT_DESCRIPTION8 = "Latest call timelines with provider latency, fallbacks, handoffs, and errors from your self-hosted trace store.";
2673
- var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3960
+ var escapeHtml11 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2674
3961
  var formatMs = (value) => typeof value === "number" ? `${value}ms` : "n/a";
2675
3962
  var formatProviders = (session) => session.providers.length ? session.providers.map((provider) => provider.provider).join(", ") : "No providers";
2676
3963
  var createVoiceTraceTimelineViewModel = (snapshot, options = {}) => {
@@ -2700,27 +3987,27 @@ var renderVoiceTraceTimelineWidgetHTML = (snapshot, options = {}) => {
2700
3987
  const model = createVoiceTraceTimelineViewModel(snapshot, options);
2701
3988
  const sessions = model.sessions.length ? `<div class="absolute-voice-trace-timeline__sessions">${model.sessions.map((session) => {
2702
3989
  const supportLinks = [
2703
- `<a href="${escapeHtml9(session.detailHref)}">Open timeline</a>`,
2704
- session.operationsRecordHref ? `<a href="${escapeHtml9(session.operationsRecordHref)}">Open operations record</a>` : undefined,
2705
- session.incidentBundleHref ? `<a href="${escapeHtml9(session.incidentBundleHref)}">Export incident bundle</a>` : undefined
3990
+ `<a href="${escapeHtml11(session.detailHref)}">Open timeline</a>`,
3991
+ session.operationsRecordHref ? `<a href="${escapeHtml11(session.operationsRecordHref)}">Open operations record</a>` : undefined,
3992
+ session.incidentBundleHref ? `<a href="${escapeHtml11(session.incidentBundleHref)}">Export incident bundle</a>` : undefined
2706
3993
  ].filter(Boolean).join("");
2707
- return `<article class="absolute-voice-trace-timeline__session absolute-voice-trace-timeline__session--${escapeHtml9(session.status)}">
3994
+ return `<article class="absolute-voice-trace-timeline__session absolute-voice-trace-timeline__session--${escapeHtml11(session.status)}">
2708
3995
  <header>
2709
- <strong>${escapeHtml9(session.sessionId)}</strong>
2710
- <span>${escapeHtml9(session.status)}</span>
3996
+ <strong>${escapeHtml11(session.sessionId)}</strong>
3997
+ <span>${escapeHtml11(session.status)}</span>
2711
3998
  </header>
2712
- <p>${escapeHtml9(session.label)} \xB7 ${escapeHtml9(session.durationLabel)} \xB7 ${escapeHtml9(session.providerLabel)}</p>
3999
+ <p>${escapeHtml11(session.label)} \xB7 ${escapeHtml11(session.durationLabel)} \xB7 ${escapeHtml11(session.providerLabel)}</p>
2713
4000
  <p class="absolute-voice-trace-timeline__actions">${supportLinks}</p>
2714
4001
  </article>`;
2715
4002
  }).join("")}</div>` : '<p class="absolute-voice-trace-timeline__empty">Run a voice session to see call timelines.</p>';
2716
- return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml9(model.status)}">
4003
+ return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml11(model.status)}">
2717
4004
  <header class="absolute-voice-trace-timeline__header">
2718
- <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml9(model.title)}</span>
2719
- <strong class="absolute-voice-trace-timeline__label">${escapeHtml9(model.label)}</strong>
4005
+ <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml11(model.title)}</span>
4006
+ <strong class="absolute-voice-trace-timeline__label">${escapeHtml11(model.label)}</strong>
2720
4007
  </header>
2721
- <p class="absolute-voice-trace-timeline__description">${escapeHtml9(model.description)}</p>
4008
+ <p class="absolute-voice-trace-timeline__description">${escapeHtml11(model.description)}</p>
2722
4009
  ${sessions}
2723
- ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml9(model.error)}</p>` : ""}
4010
+ ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml11(model.error)}</p>` : ""}
2724
4011
  </section>`;
2725
4012
  };
2726
4013
  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}`;
@@ -2882,7 +4169,7 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
2882
4169
  var DEFAULT_TITLE9 = "Turn Latency";
2883
4170
  var DEFAULT_DESCRIPTION9 = "Per-turn timing from first transcript to commit and assistant response start.";
2884
4171
  var DEFAULT_PROOF_LABEL = "Run latency proof";
2885
- var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4172
+ var escapeHtml12 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2886
4173
  var formatMs2 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
2887
4174
  var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
2888
4175
  const turns = (snapshot.report?.turns ?? []).map((turn) => ({
@@ -2910,25 +4197,25 @@ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
2910
4197
  };
2911
4198
  var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
2912
4199
  const model = createVoiceTurnLatencyViewModel(snapshot, options);
2913
- const turns = model.turns.length ? `<div class="absolute-voice-turn-latency__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-latency__turn absolute-voice-turn-latency__turn--${escapeHtml10(turn.status)}">
4200
+ 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--${escapeHtml12(turn.status)}">
2914
4201
  <header>
2915
- <strong>${escapeHtml10(turn.label)}</strong>
2916
- <span>${escapeHtml10(turn.status)}</span>
4202
+ <strong>${escapeHtml12(turn.label)}</strong>
4203
+ <span>${escapeHtml12(turn.status)}</span>
2917
4204
  </header>
2918
4205
  <dl>${turn.rows.map((row) => `<div>
2919
- <dt>${escapeHtml10(row.label)}</dt>
2920
- <dd>${escapeHtml10(row.value)}</dd>
4206
+ <dt>${escapeHtml12(row.label)}</dt>
4207
+ <dd>${escapeHtml12(row.value)}</dd>
2921
4208
  </div>`).join("")}</dl>
2922
4209
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-latency__empty">Complete a voice turn to see latency diagnostics.</p>';
2923
- return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml10(model.status)}">
4210
+ return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml12(model.status)}">
2924
4211
  <header class="absolute-voice-turn-latency__header">
2925
- <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml10(model.title)}</span>
2926
- <strong class="absolute-voice-turn-latency__label">${escapeHtml10(model.label)}</strong>
4212
+ <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml12(model.title)}</span>
4213
+ <strong class="absolute-voice-turn-latency__label">${escapeHtml12(model.label)}</strong>
2927
4214
  </header>
2928
- <p class="absolute-voice-turn-latency__description">${escapeHtml10(model.description)}</p>
2929
- ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml10(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
4215
+ <p class="absolute-voice-turn-latency__description">${escapeHtml12(model.description)}</p>
4216
+ ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml12(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
2930
4217
  ${turns}
2931
- ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml10(model.error)}</p>` : ""}
4218
+ ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml12(model.error)}</p>` : ""}
2932
4219
  </section>`;
2933
4220
  };
2934
4221
  var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {}) => {
@@ -3069,7 +4356,7 @@ var createVoiceTurnQualityStore = (path = "/api/turn-quality", options = {}) =>
3069
4356
  // src/client/turnQualityWidget.ts
3070
4357
  var DEFAULT_TITLE10 = "Turn Quality";
3071
4358
  var DEFAULT_DESCRIPTION10 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
3072
- var escapeHtml11 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4359
+ var escapeHtml13 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3073
4360
  var formatConfidence = (value) => typeof value === "number" ? `${Math.round(value * 100)}%` : "n/a";
3074
4361
  var formatMaybe = (value) => value === undefined || value === "" ? "n/a" : String(value);
3075
4362
  var getTurnDetail = (turn) => {
@@ -3119,25 +4406,25 @@ var createVoiceTurnQualityViewModel = (snapshot, options = {}) => {
3119
4406
  };
3120
4407
  var renderVoiceTurnQualityHTML = (snapshot, options = {}) => {
3121
4408
  const model = createVoiceTurnQualityViewModel(snapshot, options);
3122
- const turns = model.turns.length ? `<div class="absolute-voice-turn-quality__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-quality__turn absolute-voice-turn-quality__turn--${escapeHtml11(turn.status)}">
4409
+ 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--${escapeHtml13(turn.status)}">
3123
4410
  <header>
3124
- <strong>${escapeHtml11(turn.label)}</strong>
3125
- <span>${escapeHtml11(turn.status)}</span>
4411
+ <strong>${escapeHtml13(turn.label)}</strong>
4412
+ <span>${escapeHtml13(turn.status)}</span>
3126
4413
  </header>
3127
- <p>${escapeHtml11(turn.detail)}</p>
4414
+ <p>${escapeHtml13(turn.detail)}</p>
3128
4415
  <dl>${turn.rows.map((row) => `<div>
3129
- <dt>${escapeHtml11(row.label)}</dt>
3130
- <dd>${escapeHtml11(row.value)}</dd>
4416
+ <dt>${escapeHtml13(row.label)}</dt>
4417
+ <dd>${escapeHtml13(row.value)}</dd>
3131
4418
  </div>`).join("")}</dl>
3132
4419
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-quality__empty">Complete a voice turn to see STT quality diagnostics.</p>';
3133
- return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml11(model.status)}">
4420
+ return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml13(model.status)}">
3134
4421
  <header class="absolute-voice-turn-quality__header">
3135
- <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml11(model.title)}</span>
3136
- <strong class="absolute-voice-turn-quality__label">${escapeHtml11(model.label)}</strong>
4422
+ <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml13(model.title)}</span>
4423
+ <strong class="absolute-voice-turn-quality__label">${escapeHtml13(model.label)}</strong>
3137
4424
  </header>
3138
- <p class="absolute-voice-turn-quality__description">${escapeHtml11(model.description)}</p>
4425
+ <p class="absolute-voice-turn-quality__description">${escapeHtml13(model.description)}</p>
3139
4426
  ${turns}
3140
- ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml11(model.error)}</p>` : ""}
4427
+ ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml13(model.error)}</p>` : ""}
3141
4428
  </section>`;
3142
4429
  };
3143
4430
  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}`;
@@ -3921,6 +5208,7 @@ export {
3921
5208
  createVoiceProviderCapabilities,
3922
5209
  createVoiceOpsStatus,
3923
5210
  createVoiceOpsActionCenter,
5211
+ createVoiceLiveOps,
3924
5212
  createVoiceDeliveryRuntime,
3925
5213
  createVoiceController,
3926
5214
  createVoiceCampaignDialerProof