@absolutejs/voice 0.0.22-beta.534 → 0.0.22-beta.536

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/dist/angular/index.js +347 -347
  2. package/dist/angular/voice-cost-dashboard.service.d.ts +1 -1
  3. package/dist/angular/voice-replay-timeline.service.d.ts +1 -1
  4. package/dist/client/agentSquadStatusWidget.d.ts +3 -3
  5. package/dist/client/browserVoiceSupport.d.ts +1 -1
  6. package/dist/client/callDebugger.d.ts +2 -2
  7. package/dist/client/callDebuggerWidget.d.ts +3 -3
  8. package/dist/client/campaignDialerProof.d.ts +4 -4
  9. package/dist/client/deliveryRuntime.d.ts +4 -4
  10. package/dist/client/deliveryRuntimeWidget.d.ts +3 -3
  11. package/dist/client/htmxAttributes.d.ts +1 -5
  12. package/dist/client/htmxBootstrap.js +82 -82
  13. package/dist/client/htmxDashboardRenderers.d.ts +17 -17
  14. package/dist/client/index.js +2478 -2484
  15. package/dist/client/liveOps.d.ts +2 -2
  16. package/dist/client/liveOpsWidget.d.ts +3 -3
  17. package/dist/client/opsActionCenter.d.ts +3 -3
  18. package/dist/client/opsActionCenterWidget.d.ts +3 -3
  19. package/dist/client/opsActionHistory.d.ts +2 -2
  20. package/dist/client/opsActionHistoryWidget.d.ts +2 -2
  21. package/dist/client/opsStatus.d.ts +2 -2
  22. package/dist/client/opsStatusWidget.d.ts +4 -4
  23. package/dist/client/platformCoverage.d.ts +2 -2
  24. package/dist/client/platformCoverageWidget.d.ts +3 -3
  25. package/dist/client/profileComparison.d.ts +2 -2
  26. package/dist/client/profileComparisonWidget.d.ts +3 -3
  27. package/dist/client/profileSwitchRecommendation.d.ts +2 -2
  28. package/dist/client/profileSwitchRecommendationWidget.d.ts +3 -3
  29. package/dist/client/proofTrends.d.ts +2 -2
  30. package/dist/client/proofTrendsWidget.d.ts +3 -3
  31. package/dist/client/providerCapabilities.d.ts +2 -2
  32. package/dist/client/providerCapabilitiesWidget.d.ts +3 -3
  33. package/dist/client/providerContracts.d.ts +2 -2
  34. package/dist/client/providerContractsWidget.d.ts +3 -3
  35. package/dist/client/providerSimulationControls.d.ts +1 -1
  36. package/dist/client/providerSimulationControlsWidget.d.ts +4 -4
  37. package/dist/client/providerStatus.d.ts +2 -2
  38. package/dist/client/providerStatusWidget.d.ts +3 -3
  39. package/dist/client/readinessFailures.d.ts +2 -2
  40. package/dist/client/readinessFailuresWidget.d.ts +3 -3
  41. package/dist/client/reconnectProfileEvidence.d.ts +2 -2
  42. package/dist/client/reconnectProfileEvidenceWidget.d.ts +3 -3
  43. package/dist/client/routingStatus.d.ts +2 -2
  44. package/dist/client/routingStatusWidget.d.ts +3 -3
  45. package/dist/client/sessionObservability.d.ts +2 -2
  46. package/dist/client/sessionObservabilityWidget.d.ts +3 -3
  47. package/dist/client/sessionSnapshot.d.ts +2 -2
  48. package/dist/client/sessionSnapshotWidget.d.ts +2 -2
  49. package/dist/client/traceTimeline.d.ts +2 -2
  50. package/dist/client/traceTimelineWidget.d.ts +3 -3
  51. package/dist/client/turnLatency.d.ts +4 -4
  52. package/dist/client/turnLatencyWidget.d.ts +3 -3
  53. package/dist/client/turnQuality.d.ts +2 -2
  54. package/dist/client/turnQualityWidget.d.ts +3 -3
  55. package/dist/client/workflowStatus.d.ts +2 -2
  56. package/dist/core/agent.d.ts +1 -1
  57. package/dist/core/agentSquadContract.d.ts +2 -2
  58. package/dist/core/agentState.d.ts +1 -1
  59. package/dist/core/aiScorecard.d.ts +1 -1
  60. package/dist/core/assistant.d.ts +1 -1
  61. package/dist/core/assistantHealth.d.ts +3 -3
  62. package/dist/core/assistantMemory.d.ts +7 -7
  63. package/dist/core/audioConditioning.d.ts +1 -1
  64. package/dist/core/audit.d.ts +6 -6
  65. package/dist/core/auditDeliveryRoutes.d.ts +7 -7
  66. package/dist/core/auditExport.d.ts +10 -10
  67. package/dist/core/auditRoutes.d.ts +5 -5
  68. package/dist/core/auditSinks.d.ts +7 -7
  69. package/dist/core/bargeInRoutes.d.ts +6 -6
  70. package/dist/core/bookingFlow.d.ts +1 -1
  71. package/dist/core/browserCallProfiles.d.ts +3 -3
  72. package/dist/core/browserMediaRoutes.d.ts +5 -5
  73. package/dist/core/callDebugger.d.ts +1 -1
  74. package/dist/core/callDisposition.d.ts +1 -1
  75. package/dist/core/callScorecard.d.ts +1 -1
  76. package/dist/core/campaign.d.ts +5 -5
  77. package/dist/core/campaignControls.d.ts +1 -1
  78. package/dist/core/campaignDialers.d.ts +4 -4
  79. package/dist/core/campaignTemplate.d.ts +1 -1
  80. package/dist/core/competitiveCoverage.d.ts +2 -2
  81. package/dist/core/conversationSimulator.d.ts +1 -1
  82. package/dist/core/correction.d.ts +1 -1
  83. package/dist/core/dataControl.d.ts +5 -5
  84. package/dist/core/deliveryRuntime.d.ts +7 -7
  85. package/dist/core/deliverySinkRoutes.d.ts +7 -7
  86. package/dist/core/demoReadyRoutes.d.ts +1 -1
  87. package/dist/core/dncRegistry.d.ts +1 -1
  88. package/dist/core/dtmfCollector.d.ts +1 -1
  89. package/dist/core/evalRoutes.d.ts +16 -16
  90. package/dist/core/fileStore.d.ts +18 -18
  91. package/dist/core/guardrails.d.ts +2 -2
  92. package/dist/core/handoff.d.ts +4 -4
  93. package/dist/core/handoffHealth.d.ts +4 -4
  94. package/dist/core/htmx.d.ts +1 -1
  95. package/dist/core/incidentBundle.d.ts +1 -1
  96. package/dist/core/incidentTimeline.d.ts +11 -11
  97. package/dist/core/latencySlo.d.ts +1 -1
  98. package/dist/core/liveCoach.d.ts +1 -1
  99. package/dist/core/liveLatency.d.ts +3 -3
  100. package/dist/core/liveOps.d.ts +6 -6
  101. package/dist/core/mediaPipelineRoutes.d.ts +4 -4
  102. package/dist/core/monitor.d.ts +1 -1
  103. package/dist/core/multilingualProof.d.ts +1 -1
  104. package/dist/core/observabilityExport.d.ts +15 -15
  105. package/dist/core/operationalStatus.d.ts +3 -3
  106. package/dist/core/operationsRecord.d.ts +8 -8
  107. package/dist/core/ops.d.ts +58 -58
  108. package/dist/core/opsActionAuditRoutes.d.ts +10 -10
  109. package/dist/core/opsConsoleRoutes.d.ts +3 -3
  110. package/dist/core/opsRecovery.d.ts +4 -4
  111. package/dist/core/opsSinks.d.ts +6 -6
  112. package/dist/core/opsStatusRoutes.d.ts +3 -3
  113. package/dist/core/opsWebhook.d.ts +9 -9
  114. package/dist/core/otelExporter.d.ts +1 -1
  115. package/dist/core/outcomeContract.d.ts +6 -6
  116. package/dist/core/pathway.d.ts +2 -2
  117. package/dist/core/pathwayRuntime.d.ts +2 -2
  118. package/dist/core/phoneAgent.d.ts +2 -2
  119. package/dist/core/phoneAgentProductionSmoke.d.ts +7 -7
  120. package/dist/core/platformCoverage.d.ts +1 -1
  121. package/dist/core/postCallSurvey.d.ts +1 -1
  122. package/dist/core/postgresStore.d.ts +8 -8
  123. package/dist/core/productionReadiness.d.ts +9 -9
  124. package/dist/core/profileSwitchRecommendation.d.ts +9 -9
  125. package/dist/core/proofAssertions.d.ts +1 -1
  126. package/dist/core/proofPack.d.ts +12 -12
  127. package/dist/core/proofRunner.d.ts +2 -2
  128. package/dist/core/proofTrends.d.ts +26 -26
  129. package/dist/core/providerCapabilities.d.ts +5 -5
  130. package/dist/core/providerDecisionTraces.d.ts +4 -4
  131. package/dist/core/providerHealth.d.ts +3 -3
  132. package/dist/core/providerOrchestration.d.ts +4 -4
  133. package/dist/core/providerRouterTraces.d.ts +2 -2
  134. package/dist/core/providerRoutingContract.d.ts +2 -2
  135. package/dist/core/providerSlo.d.ts +5 -5
  136. package/dist/core/providerStackRecommendations.d.ts +8 -8
  137. package/dist/core/qualityRoutes.d.ts +3 -3
  138. package/dist/core/queue.d.ts +26 -26
  139. package/dist/core/realtimeChannel.d.ts +5 -5
  140. package/dist/core/realtimeProviderContracts.d.ts +3 -3
  141. package/dist/core/reconnectContract.d.ts +16 -16
  142. package/dist/core/recordingStore.d.ts +2 -2
  143. package/dist/core/reminderScheduler.d.ts +1 -1
  144. package/dist/core/resilienceRoutes.d.ts +1 -1
  145. package/dist/core/routing.d.ts +1 -1
  146. package/dist/core/sessionObservability.d.ts +2 -2
  147. package/dist/core/sessionReplay.d.ts +12 -12
  148. package/dist/core/sessionSnapshot.d.ts +1 -1
  149. package/dist/core/simulationSuite.d.ts +4 -4
  150. package/dist/core/sloCalibration.d.ts +12 -12
  151. package/dist/core/sqliteStore.d.ts +8 -8
  152. package/dist/core/telephonyMediaRoutes.d.ts +4 -4
  153. package/dist/core/telephonyOutcome.d.ts +2 -2
  154. package/dist/core/toolContract.d.ts +10 -10
  155. package/dist/core/toolRuntime.d.ts +1 -1
  156. package/dist/core/trace.d.ts +18 -18
  157. package/dist/core/traceDeliveryRoutes.d.ts +7 -7
  158. package/dist/core/traceTimeline.d.ts +3 -3
  159. package/dist/core/turnLatency.d.ts +4 -4
  160. package/dist/core/turnQuality.d.ts +5 -5
  161. package/dist/core/types.d.ts +7 -2
  162. package/dist/core/voiceMonitoring.d.ts +11 -11
  163. package/dist/core/webhookVerification.d.ts +4 -4
  164. package/dist/core/whisperChannel.d.ts +4 -4
  165. package/dist/core/workflowContract.d.ts +13 -13
  166. package/dist/core/zeroDataRetention.d.ts +3 -13
  167. package/dist/drizzle/assistantMemory.d.ts +95 -0
  168. package/dist/drizzle/eval.d.ts +61 -0
  169. package/dist/drizzle/handoff.d.ts +62 -0
  170. package/dist/drizzle/incidentBundle.d.ts +61 -0
  171. package/dist/drizzle/index.d.ts +1088 -0
  172. package/dist/drizzle/index.js +3064 -0
  173. package/dist/drizzle/observabilityExport.d.ts +61 -0
  174. package/dist/drizzle/proofTrends.d.ts +126 -0
  175. package/dist/drizzle/runtimeStorage.d.ts +1315 -0
  176. package/dist/drizzle/shared.d.ts +76 -0
  177. package/dist/embed/index.js +72 -72
  178. package/dist/embed/voice-widget.js +2 -2
  179. package/dist/index.js +7034 -7101
  180. package/dist/react/index.js +2148 -2150
  181. package/dist/svelte/createVoiceAgentSquadStatus.d.ts +2 -2
  182. package/dist/svelte/createVoiceCallDebugger.d.ts +1 -1
  183. package/dist/svelte/createVoiceCallPlayer.d.ts +8 -8
  184. package/dist/svelte/createVoiceCampaignDialerProof.d.ts +2 -2
  185. package/dist/svelte/createVoiceCostDashboard.d.ts +4 -4
  186. package/dist/svelte/createVoiceDeliveryRuntime.d.ts +3 -3
  187. package/dist/svelte/createVoiceLiveAgentConsole.d.ts +4 -4
  188. package/dist/svelte/createVoiceLiveOps.d.ts +4 -4
  189. package/dist/svelte/createVoiceOpsActionCenter.d.ts +2 -2
  190. package/dist/svelte/createVoiceOpsStatus.d.ts +2 -2
  191. package/dist/svelte/createVoiceProviderCapabilities.d.ts +1 -1
  192. package/dist/svelte/createVoiceProviderContracts.d.ts +1 -1
  193. package/dist/svelte/createVoiceProviderSimulationControls.d.ts +1 -1
  194. package/dist/svelte/createVoiceProviderStatus.d.ts +1 -1
  195. package/dist/svelte/createVoiceReplayTimeline.d.ts +1 -1
  196. package/dist/svelte/createVoiceRoutingStatus.d.ts +1 -1
  197. package/dist/svelte/createVoiceSessionObservability.d.ts +1 -1
  198. package/dist/svelte/createVoiceSessionSnapshot.d.ts +1 -1
  199. package/dist/svelte/createVoiceTraceTimeline.d.ts +1 -1
  200. package/dist/svelte/createVoiceTurnLatency.d.ts +2 -2
  201. package/dist/svelte/createVoiceTurnQuality.d.ts +1 -1
  202. package/dist/svelte/createVoiceWidget.d.ts +2 -2
  203. package/dist/svelte/createVoiceWorkflowStatus.d.ts +1 -1
  204. package/dist/svelte/index.js +1518 -1522
  205. package/dist/telephony/matrix.d.ts +3 -3
  206. package/dist/telephony/plivo.d.ts +2 -2
  207. package/dist/telephony/security.d.ts +3 -3
  208. package/dist/telephony/telnyx.d.ts +1 -1
  209. package/dist/telephony/twilio.d.ts +2 -2
  210. package/dist/testing/benchmark.d.ts +6 -6
  211. package/dist/testing/corrected.d.ts +4 -4
  212. package/dist/testing/duplex.d.ts +2 -2
  213. package/dist/testing/fixtures.d.ts +1 -1
  214. package/dist/testing/index.js +1382 -1388
  215. package/dist/testing/review.d.ts +4 -4
  216. package/dist/testing/sessionBenchmark.d.ts +10 -10
  217. package/dist/testing/telephony.d.ts +3 -3
  218. package/dist/testing/tts.d.ts +1 -1
  219. package/dist/vue/VoiceCostDashboard.d.ts +2 -2
  220. package/dist/vue/index.js +2110 -2112
  221. package/dist/vue/useVoiceController.d.ts +5 -5
  222. package/dist/vue/useVoiceDeliveryRuntime.d.ts +1 -1
  223. package/dist/vue/useVoiceStream.d.ts +5 -5
  224. package/package.json +28 -6
@@ -0,0 +1,3064 @@
1
+ // @bun
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __name = (target, name) => {
6
+ Object.defineProperty(target, "name", {
7
+ value: name,
8
+ enumerable: false,
9
+ configurable: true
10
+ });
11
+ return target;
12
+ };
13
+ var __returnValue = (v) => v;
14
+ function __exportSetter(name, newValue) {
15
+ this[name] = __returnValue.bind(null, newValue);
16
+ }
17
+ var __export = (target, all) => {
18
+ for (var name in all)
19
+ __defProp(target, name, {
20
+ get: all[name],
21
+ enumerable: true,
22
+ configurable: true,
23
+ set: __exportSetter.bind(all, name)
24
+ });
25
+ };
26
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
27
+ var __typeError = (msg) => {
28
+ throw TypeError(msg);
29
+ };
30
+ var __defNormalProp = (obj, key, value) => (key in obj) ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
31
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
32
+ var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
33
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
34
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
35
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
36
+ var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
37
+ var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
38
+ var __expectFn = (fn) => fn !== undefined && typeof fn !== "function" ? __typeError("Function expected") : fn;
39
+ var __decoratorContext = (kind, name, done, metadata, fns) => ({
40
+ kind: __decoratorStrings[kind],
41
+ name,
42
+ metadata,
43
+ addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null))
44
+ });
45
+ var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
46
+ var __runInitializers = (array, flags, self, value) => {
47
+ for (var i = 0, fns = array[flags >> 1], n = fns && fns.length;i < n; i++)
48
+ flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
49
+ return value;
50
+ };
51
+ var __decorateElement = (array, flags, name, decorators, target, extra) => {
52
+ var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
53
+ var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
54
+ var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
55
+ var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : {
56
+ get [name]() {
57
+ return __privateGet(this, extra);
58
+ },
59
+ set [name](x) {
60
+ __privateSet(this, extra, x);
61
+ }
62
+ }, name));
63
+ k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
64
+ for (var i = decorators.length - 1;i >= 0; i--) {
65
+ ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
66
+ if (k) {
67
+ ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => (name in x) };
68
+ if (k ^ 3)
69
+ access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
70
+ if (k > 2)
71
+ access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
72
+ }
73
+ it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? undefined : { get: desc.get, set: desc.set } : target, ctx);
74
+ done._ = 1;
75
+ if (k ^ 4 || it === undefined)
76
+ __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
77
+ else if (typeof it !== "object" || it === null)
78
+ __typeError("Object expected");
79
+ else
80
+ __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
81
+ }
82
+ return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
83
+ };
84
+ var __require = import.meta.require;
85
+
86
+ // src/drizzle/assistantMemory.ts
87
+ import { and, desc, eq } from "drizzle-orm";
88
+ import { bigint, jsonb, pgTable, primaryKey, text } from "drizzle-orm/pg-core";
89
+
90
+ // src/core/assistantMemory.ts
91
+ var createMemoryId = (input) => `${input.assistantId}:${input.namespace}:${input.key}`;
92
+ var createVoiceAssistantMemoryHandle = async (input) => {
93
+ const namespace = await resolveVoiceAssistantMemoryNamespace({
94
+ assistantId: input.assistantId,
95
+ context: input.context,
96
+ memory: input.memory,
97
+ session: input.session
98
+ });
99
+ const trace = async (event) => {
100
+ await input.trace?.append({
101
+ at: Date.now(),
102
+ payload: {
103
+ assistantId: input.assistantId,
104
+ namespace,
105
+ ...event
106
+ },
107
+ scenarioId: input.session.scenarioId,
108
+ sessionId: input.session.id,
109
+ type: "assistant.memory"
110
+ });
111
+ };
112
+ return {
113
+ namespace,
114
+ delete: async (key) => {
115
+ await input.memory.store.delete({
116
+ assistantId: input.assistantId,
117
+ key,
118
+ namespace
119
+ });
120
+ await trace({
121
+ action: "delete",
122
+ key
123
+ });
124
+ },
125
+ get: async (key) => {
126
+ const record = await input.memory.store.get({
127
+ assistantId: input.assistantId,
128
+ key,
129
+ namespace
130
+ });
131
+ await trace({
132
+ action: "get",
133
+ found: Boolean(record),
134
+ key
135
+ });
136
+ return record?.value;
137
+ },
138
+ list: async () => {
139
+ const records = await input.memory.store.list({
140
+ assistantId: input.assistantId,
141
+ namespace
142
+ });
143
+ await trace({
144
+ action: "list",
145
+ count: records.length
146
+ });
147
+ return records;
148
+ },
149
+ set: async (key, value, metadata) => {
150
+ const record = await input.memory.store.set({
151
+ assistantId: input.assistantId,
152
+ key,
153
+ metadata,
154
+ namespace,
155
+ value
156
+ });
157
+ await trace({
158
+ action: "set",
159
+ key
160
+ });
161
+ return record;
162
+ }
163
+ };
164
+ };
165
+ var createVoiceAssistantMemoryRecord = (input) => {
166
+ const now = Date.now();
167
+ return {
168
+ ...input,
169
+ createdAt: input.createdAt ?? input.updatedAt ?? now,
170
+ updatedAt: input.updatedAt ?? now
171
+ };
172
+ };
173
+ var createVoiceMemoryAssistantMemoryStore = () => {
174
+ const records = new Map;
175
+ return {
176
+ delete: async (input) => {
177
+ records.delete(createMemoryId(input));
178
+ },
179
+ get: async (input) => records.get(createMemoryId(input)),
180
+ list: async (input) => [...records.values()].filter((record) => record.assistantId === input.assistantId && (input.namespace === undefined || record.namespace === input.namespace)).sort((left, right) => right.updatedAt - left.updatedAt),
181
+ set: async (input) => {
182
+ const id = createMemoryId(input);
183
+ const existing = records.get(id);
184
+ const record = createVoiceAssistantMemoryRecord({
185
+ ...input,
186
+ createdAt: input.createdAt ?? existing?.createdAt,
187
+ updatedAt: input.updatedAt
188
+ });
189
+ records.set(id, record);
190
+ return record;
191
+ }
192
+ };
193
+ };
194
+ var resolveVoiceAssistantMemoryNamespace = async (input) => typeof input.memory.namespace === "function" ? await input.memory.namespace(input) : input.memory.namespace;
195
+
196
+ // src/drizzle/assistantMemory.ts
197
+ var voiceAssistantMemoryTable = pgTable("voice_assistant_memory", {
198
+ assistantId: text("assistant_id").notNull(),
199
+ key: text("key").notNull(),
200
+ namespace: text("namespace").notNull(),
201
+ payload: jsonb("payload").notNull(),
202
+ sortAt: bigint("sort_at", { mode: "number" }).notNull()
203
+ }, (table) => [
204
+ primaryKey({
205
+ columns: [table.assistantId, table.namespace, table.key]
206
+ })
207
+ ]);
208
+ var createDrizzleAssistantMemoryStore = (db) => {
209
+ const get = async (input) => {
210
+ const rows = await db.select({ payload: voiceAssistantMemoryTable.payload }).from(voiceAssistantMemoryTable).where(and(eq(voiceAssistantMemoryTable.assistantId, input.assistantId), eq(voiceAssistantMemoryTable.namespace, input.namespace), eq(voiceAssistantMemoryTable.key, input.key))).limit(1);
211
+ return rows[0]?.payload;
212
+ };
213
+ return {
214
+ get,
215
+ delete: async (input) => {
216
+ await db.delete(voiceAssistantMemoryTable).where(and(eq(voiceAssistantMemoryTable.assistantId, input.assistantId), eq(voiceAssistantMemoryTable.namespace, input.namespace), eq(voiceAssistantMemoryTable.key, input.key)));
217
+ },
218
+ list: async (input) => {
219
+ const rows = await db.select({ payload: voiceAssistantMemoryTable.payload }).from(voiceAssistantMemoryTable).where(input.namespace === undefined ? eq(voiceAssistantMemoryTable.assistantId, input.assistantId) : and(eq(voiceAssistantMemoryTable.assistantId, input.assistantId), eq(voiceAssistantMemoryTable.namespace, input.namespace))).orderBy(desc(voiceAssistantMemoryTable.sortAt));
220
+ return rows.map((row) => row.payload);
221
+ },
222
+ set: async (input) => {
223
+ const existing = await get(input);
224
+ const record = createVoiceAssistantMemoryRecord({
225
+ ...input,
226
+ createdAt: input.createdAt ?? existing?.createdAt,
227
+ updatedAt: input.updatedAt
228
+ });
229
+ await db.insert(voiceAssistantMemoryTable).values({
230
+ assistantId: record.assistantId,
231
+ key: record.key,
232
+ namespace: record.namespace,
233
+ payload: record,
234
+ sortAt: record.updatedAt
235
+ }).onConflictDoUpdate({
236
+ set: {
237
+ payload: record,
238
+ sortAt: record.updatedAt
239
+ },
240
+ target: [
241
+ voiceAssistantMemoryTable.assistantId,
242
+ voiceAssistantMemoryTable.namespace,
243
+ voiceAssistantMemoryTable.key
244
+ ]
245
+ });
246
+ return record;
247
+ }
248
+ };
249
+ };
250
+ var createVoiceDrizzleAssistantMemoryStore = (options) => createDrizzleAssistantMemoryStore(options.db);
251
+
252
+ // src/drizzle/shared.ts
253
+ import { desc as desc2, eq as eq2 } from "drizzle-orm";
254
+ import {
255
+ bigint as bigint2,
256
+ jsonb as jsonb2,
257
+ pgTable as pgTable2,
258
+ text as text2
259
+ } from "drizzle-orm/pg-core";
260
+ var voiceDocumentTable = (name) => pgTable2(name, {
261
+ id: text2("id").primaryKey(),
262
+ payload: jsonb2("payload").notNull(),
263
+ sortAt: bigint2("sort_at", { mode: "number" }).notNull()
264
+ });
265
+ var createVoiceDrizzleRecordStore = (input) => {
266
+ const get = async (id) => {
267
+ const rows = await input.db.select({ payload: input.table.payload }).from(input.table).where(eq2(input.table.id, id)).limit(1);
268
+ return rows[0]?.payload;
269
+ };
270
+ const list = async () => {
271
+ const rows = await input.db.select({ payload: input.table.payload }).from(input.table).orderBy(desc2(input.table.sortAt), desc2(input.table.id));
272
+ return rows.map((row) => row.payload);
273
+ };
274
+ const set = async (id, value) => {
275
+ const decorated = input.decorate(id, value);
276
+ await input.db.insert(input.table).values({
277
+ id,
278
+ payload: decorated,
279
+ sortAt: input.getSortAt(decorated)
280
+ }).onConflictDoUpdate({
281
+ set: {
282
+ payload: decorated,
283
+ sortAt: input.getSortAt(decorated)
284
+ },
285
+ target: input.table.id
286
+ });
287
+ };
288
+ const remove = async (id) => {
289
+ await input.db.delete(input.table).where(eq2(input.table.id, id));
290
+ };
291
+ return { get, list, remove, set };
292
+ };
293
+
294
+ // src/drizzle/eval.ts
295
+ var voiceEvalBaselineTable = voiceDocumentTable("voice_eval_baseline");
296
+ var VOICE_EVAL_BASELINE_ID = "baseline";
297
+ var createDrizzleEvalBaselineStore = (db) => {
298
+ const store = createVoiceDrizzleRecordStore({
299
+ db,
300
+ decorate: (_id, value) => value,
301
+ getSortAt: (value) => value.checkedAt,
302
+ table: voiceEvalBaselineTable
303
+ });
304
+ return {
305
+ get: async () => store.get(VOICE_EVAL_BASELINE_ID),
306
+ set: async (report) => {
307
+ await store.set(VOICE_EVAL_BASELINE_ID, report);
308
+ }
309
+ };
310
+ };
311
+ var createVoiceDrizzleEvalBaselineStore = (options) => createDrizzleEvalBaselineStore(options.db);
312
+
313
+ // src/drizzle/handoff.ts
314
+ var voiceHandoffDeliveriesTable = voiceDocumentTable("voice_handoff_deliveries");
315
+ var createDrizzleHandoffDeliveryStore = (db) => createVoiceDrizzleRecordStore({
316
+ db,
317
+ decorate: (_id, value) => value,
318
+ getSortAt: (value) => value.createdAt,
319
+ table: voiceHandoffDeliveriesTable
320
+ });
321
+ var createVoiceDrizzleHandoffDeliveryStore = (options) => createDrizzleHandoffDeliveryStore(options.db);
322
+
323
+ // src/drizzle/incidentBundle.ts
324
+ var voiceIncidentBundlesTable = voiceDocumentTable("voice_incident_bundles");
325
+ var matchesIncidentBundleFilter = (artifact, filter) => {
326
+ if (filter.sessionId && artifact.sessionId !== filter.sessionId) {
327
+ return false;
328
+ }
329
+ if (typeof filter.expiredAt === "number" && (artifact.expiresAt === undefined || artifact.expiresAt > filter.expiredAt)) {
330
+ return false;
331
+ }
332
+ return true;
333
+ };
334
+ var createVoiceDrizzleIncidentBundleStore = (options) => {
335
+ const store = createVoiceDrizzleRecordStore({
336
+ db: options.db,
337
+ decorate: (id, value) => ({
338
+ ...value,
339
+ id
340
+ }),
341
+ getSortAt: (value) => value.createdAt,
342
+ table: voiceIncidentBundlesTable
343
+ });
344
+ return {
345
+ get: store.get,
346
+ remove: store.remove,
347
+ set: store.set,
348
+ list: async (filter = {}) => (await store.list()).filter((artifact) => matchesIncidentBundleFilter(artifact, filter))
349
+ };
350
+ };
351
+
352
+ // src/drizzle/observabilityExport.ts
353
+ var voiceObservabilityExportDeliveryReceiptsTable = voiceDocumentTable("voice_observability_export_receipts");
354
+ var createVoiceDrizzleObservabilityExportDeliveryReceiptStore = (options) => createVoiceDrizzleRecordStore({
355
+ db: options.db,
356
+ decorate: (id, value) => ({
357
+ ...value,
358
+ id
359
+ }),
360
+ getSortAt: (value) => value.checkedAt,
361
+ table: voiceObservabilityExportDeliveryReceiptsTable
362
+ });
363
+
364
+ // src/drizzle/proofTrends.ts
365
+ var voiceRealCallProfileEvidenceTable = voiceDocumentTable("voice_real_call_profile_evidence");
366
+ var voiceRealCallProfileRecoveryJobsTable = voiceDocumentTable("voice_real_call_profile_recovery_jobs");
367
+ var parseRealCallProfileEvidenceBoundary = (value) => {
368
+ if (value === undefined) {
369
+ return;
370
+ }
371
+ if (value instanceof Date) {
372
+ return value.getTime();
373
+ }
374
+ if (typeof value === "number") {
375
+ return value;
376
+ }
377
+ return Date.parse(value);
378
+ };
379
+ var readRealCallProfileEvidenceSortTime = (evidence, fallback) => Date.parse(evidence.generatedAt ?? fallback) || Date.parse(fallback);
380
+ var matchesRealCallProfileEvidenceListOptions = (record, input) => {
381
+ const evidenceTime = readRealCallProfileEvidenceSortTime(record, record.createdAt);
382
+ const since = parseRealCallProfileEvidenceBoundary(input.since);
383
+ const until = parseRealCallProfileEvidenceBoundary(input.until);
384
+ return (!input.profileId || record.profileId === input.profileId) && (!input.sessionId || record.sessionId === input.sessionId) && (since === undefined || Number.isNaN(since) || evidenceTime >= since) && (until === undefined || Number.isNaN(until) || evidenceTime <= until);
385
+ };
386
+ var matchesRealCallProfileRecoveryJobListOptions = (job, input) => (!input.actionId || job.actionId === input.actionId) && (!input.status || job.status === input.status);
387
+ var createDrizzleRealCallProfileRecoveryJobStore = (db, options = {}) => {
388
+ const store = createVoiceDrizzleRecordStore({
389
+ db,
390
+ decorate: (_id, value) => value,
391
+ getSortAt: (value) => Date.parse(value.updatedAt) || Date.now(),
392
+ table: voiceRealCallProfileRecoveryJobsTable
393
+ });
394
+ const now = () => (options.now ?? (() => new Date))().toISOString();
395
+ const createId = () => `${options.idPrefix ?? "voice-recovery-job"}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
396
+ return {
397
+ get: store.get,
398
+ create: async (input) => {
399
+ const createdAt = input.createdAt ?? now();
400
+ const job = {
401
+ actionId: input.actionId,
402
+ createdAt,
403
+ id: input.id ?? createId(),
404
+ message: input.message,
405
+ status: input.status ?? "queued",
406
+ updatedAt: createdAt
407
+ };
408
+ await store.set(job.id, job);
409
+ return job;
410
+ },
411
+ list: async (input = {}) => {
412
+ const limit = Number.isFinite(input.limit) && input.limit !== undefined && input.limit > 0 ? Math.floor(input.limit) : 50;
413
+ return (await store.list()).filter((job) => matchesRealCallProfileRecoveryJobListOptions(job, input)).slice(0, limit);
414
+ },
415
+ update: async (id, update) => {
416
+ const existing = await store.get(id);
417
+ if (!existing) {
418
+ return;
419
+ }
420
+ const next = {
421
+ ...existing,
422
+ ...update,
423
+ updatedAt: update.updatedAt ?? now()
424
+ };
425
+ await store.set(id, next);
426
+ return next;
427
+ }
428
+ };
429
+ };
430
+ var createDrizzleRealCallProfileEvidenceStore = (db, options = {}) => {
431
+ const store = createVoiceDrizzleRecordStore({
432
+ db,
433
+ decorate: (_id, value) => value,
434
+ getSortAt: (value) => readRealCallProfileEvidenceSortTime(value, value.createdAt),
435
+ table: voiceRealCallProfileEvidenceTable
436
+ });
437
+ const now = () => (options.now ?? (() => new Date))().toISOString();
438
+ const createId = () => `${options.idPrefix ?? "voice-profile-evidence"}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
439
+ return {
440
+ get: store.get,
441
+ remove: store.remove,
442
+ append: async (input) => {
443
+ const record = {
444
+ ...input,
445
+ createdAt: input.createdAt ?? now(),
446
+ id: input.id ?? createId()
447
+ };
448
+ await store.set(record.id, record);
449
+ return record;
450
+ },
451
+ list: async (input = {}) => {
452
+ const limit = Number.isFinite(input.limit) && input.limit !== undefined && input.limit > 0 ? Math.floor(input.limit) : 500;
453
+ return (await store.list()).filter((record) => matchesRealCallProfileEvidenceListOptions(record, input)).slice(0, limit);
454
+ }
455
+ };
456
+ };
457
+ var createVoiceDrizzleRealCallProfileEvidenceStore = (options) => createDrizzleRealCallProfileEvidenceStore(options.db, {
458
+ idPrefix: options.idPrefix,
459
+ now: options.now
460
+ });
461
+ var createVoiceDrizzleRealCallProfileRecoveryJobStore = (options) => createDrizzleRealCallProfileRecoveryJobStore(options.db, {
462
+ idPrefix: options.idPrefix,
463
+ now: options.now
464
+ });
465
+
466
+ // src/core/audit.ts
467
+ var includes = (filter, value) => {
468
+ if (!filter) {
469
+ return true;
470
+ }
471
+ if (!value) {
472
+ return false;
473
+ }
474
+ return Array.isArray(filter) ? filter.includes(value) : filter === value;
475
+ };
476
+ var createVoiceAuditEvent = (event) => ({
477
+ ...event,
478
+ at: event.at ?? Date.now(),
479
+ id: event.id ?? crypto.randomUUID()
480
+ });
481
+ var createVoiceAuditLogger = (store) => ({
482
+ handoff: (input) => recordVoiceHandoffAuditEvent({ ...input, store }),
483
+ operatorAction: (input) => recordVoiceOperatorAuditEvent({ ...input, store }),
484
+ providerCall: (input) => recordVoiceProviderAuditEvent({ ...input, store }),
485
+ record: (event) => recordVoiceAuditEvent(store, event),
486
+ retention: (input) => recordVoiceRetentionAuditEvent({ ...input, store }),
487
+ toolCall: (input) => recordVoiceToolAuditEvent({ ...input, store })
488
+ });
489
+ var createVoiceMemoryAuditEventStore = () => {
490
+ const events = new Map;
491
+ return {
492
+ append: (event) => {
493
+ const stored = createVoiceAuditEvent(event);
494
+ events.set(stored.id, stored);
495
+ return stored;
496
+ },
497
+ get: (id) => events.get(id),
498
+ list: (filter) => filterVoiceAuditEvents([...events.values()], filter)
499
+ };
500
+ };
501
+ var createVoiceScopedAuditEventStore = (store, scope) => {
502
+ const upstreamFilter = (filter = {}) => {
503
+ const next = { ...filter };
504
+ delete next.limit;
505
+ if (scope.actorId !== undefined) {
506
+ delete next.actorId;
507
+ }
508
+ if (scope.outcome !== undefined) {
509
+ delete next.outcome;
510
+ }
511
+ if (scope.resourceId !== undefined) {
512
+ delete next.resourceId;
513
+ }
514
+ if (scope.resourceType !== undefined) {
515
+ delete next.resourceType;
516
+ }
517
+ if (scope.sessionId !== undefined) {
518
+ delete next.sessionId;
519
+ }
520
+ if (scope.traceId !== undefined) {
521
+ delete next.traceId;
522
+ }
523
+ if (scope.type !== undefined) {
524
+ delete next.type;
525
+ }
526
+ return next;
527
+ };
528
+ const scopedFilter = (filter = {}) => ({
529
+ ...filter,
530
+ ...scope
531
+ });
532
+ return {
533
+ append: (event) => store.append(event),
534
+ get: (id) => store.get(id),
535
+ list: async (filter) => filterVoiceAuditEvents(await store.list(upstreamFilter(filter)), scopedFilter(filter))
536
+ };
537
+ };
538
+ var filterVoiceAuditEvents = (events, filter = {}) => {
539
+ const sorted = events.filter((event) => {
540
+ if (!includes(filter.type, event.type)) {
541
+ return false;
542
+ }
543
+ if (!includes(filter.outcome, event.outcome)) {
544
+ return false;
545
+ }
546
+ if (filter.actorId && event.actor?.id !== filter.actorId) {
547
+ return false;
548
+ }
549
+ if (filter.resourceId && event.resource?.id !== filter.resourceId) {
550
+ return false;
551
+ }
552
+ if (filter.resourceType && event.resource?.type !== filter.resourceType) {
553
+ return false;
554
+ }
555
+ if (filter.sessionId && event.sessionId !== filter.sessionId) {
556
+ return false;
557
+ }
558
+ if (filter.traceId && event.traceId !== filter.traceId) {
559
+ return false;
560
+ }
561
+ if (typeof filter.after === "number" && event.at <= filter.after) {
562
+ return false;
563
+ }
564
+ if (typeof filter.afterOrAt === "number" && event.at < filter.afterOrAt) {
565
+ return false;
566
+ }
567
+ if (typeof filter.before === "number" && event.at >= filter.before) {
568
+ return false;
569
+ }
570
+ if (typeof filter.beforeOrAt === "number" && event.at > filter.beforeOrAt) {
571
+ return false;
572
+ }
573
+ return true;
574
+ }).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
575
+ return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
576
+ };
577
+ var recordVoiceAuditEvent = (store, event) => store.append(createVoiceAuditEvent(event));
578
+ var recordVoiceHandoffAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
579
+ action: "handoff",
580
+ actor: input.actor,
581
+ metadata: input.metadata,
582
+ outcome: input.outcome,
583
+ payload: {
584
+ fromAgentId: input.fromAgentId,
585
+ reason: input.reason,
586
+ target: input.target,
587
+ toAgentId: input.toAgentId
588
+ },
589
+ resource: {
590
+ id: input.toAgentId ?? input.target,
591
+ type: "handoff"
592
+ },
593
+ sessionId: input.sessionId,
594
+ traceId: input.traceId,
595
+ type: "handoff"
596
+ });
597
+ var recordVoiceOperatorAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
598
+ action: input.action,
599
+ actor: input.actor,
600
+ metadata: input.metadata,
601
+ outcome: input.outcome ?? "success",
602
+ payload: input.payload,
603
+ resource: input.resource,
604
+ sessionId: input.sessionId,
605
+ traceId: input.traceId,
606
+ type: "operator.action"
607
+ });
608
+ var recordVoiceProviderAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
609
+ action: `${input.kind}.provider.call`,
610
+ actor: input.actor,
611
+ metadata: input.metadata,
612
+ outcome: input.outcome,
613
+ payload: {
614
+ cost: input.cost,
615
+ elapsedMs: input.elapsedMs,
616
+ error: input.error,
617
+ kind: input.kind,
618
+ model: input.model,
619
+ provider: input.provider
620
+ },
621
+ resource: {
622
+ id: input.provider,
623
+ type: "provider"
624
+ },
625
+ sessionId: input.sessionId,
626
+ traceId: input.traceId,
627
+ type: "provider.call"
628
+ });
629
+ var recordVoiceRetentionAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
630
+ action: input.dryRun ? "retention.plan" : "retention.apply",
631
+ actor: input.actor ?? {
632
+ id: "voice-retention",
633
+ kind: "system"
634
+ },
635
+ metadata: input.metadata,
636
+ outcome: "success",
637
+ payload: {
638
+ deletedCount: input.report.deletedCount,
639
+ dryRun: input.dryRun,
640
+ scopes: input.report.scopes
641
+ },
642
+ resource: {
643
+ type: "retention-policy"
644
+ },
645
+ type: "retention.policy"
646
+ });
647
+ var recordVoiceToolAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
648
+ action: "tool.call",
649
+ actor: input.actor,
650
+ metadata: input.metadata,
651
+ outcome: input.outcome,
652
+ payload: {
653
+ elapsedMs: input.elapsedMs,
654
+ error: input.error,
655
+ toolCallId: input.toolCallId,
656
+ toolName: input.toolName
657
+ },
658
+ resource: {
659
+ id: input.toolName,
660
+ type: "tool"
661
+ },
662
+ sessionId: input.sessionId,
663
+ traceId: input.traceId,
664
+ type: "tool.call"
665
+ });
666
+
667
+ // src/core/ops.ts
668
+ var createVoiceExternalObjectMap = (input) => {
669
+ const at = input.at ?? Date.now();
670
+ return {
671
+ createdAt: at,
672
+ externalId: input.externalId,
673
+ id: createVoiceExternalObjectMapId(input),
674
+ provider: input.provider,
675
+ sinkId: input.sinkId,
676
+ sourceId: input.sourceId,
677
+ sourceType: input.sourceType,
678
+ updatedAt: at
679
+ };
680
+ };
681
+ var createVoiceExternalObjectMapId = (input) => [
682
+ input.provider,
683
+ input.sinkId ?? "default",
684
+ encodeURIComponent(input.sourceId)
685
+ ].join(":");
686
+ var sleep = async (delayMs) => {
687
+ if (delayMs <= 0) {
688
+ return;
689
+ }
690
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
691
+ };
692
+ var toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
693
+ var signVoiceIntegrationWebhookBody = async (input) => {
694
+ const encoder = new TextEncoder;
695
+ const key = await crypto.subtle.importKey("raw", encoder.encode(input.secret), {
696
+ hash: "SHA-256",
697
+ name: "HMAC"
698
+ }, false, ["sign"]);
699
+ const payload = encoder.encode(`${input.timestamp}.${input.body}`);
700
+ const signature = await crypto.subtle.sign("HMAC", key, payload);
701
+ return `sha256=${toHex(new Uint8Array(signature))}`;
702
+ };
703
+ var createVoiceWebhookDeliveryError = (input) => {
704
+ if (input.response) {
705
+ const statusText = input.response.statusText?.trim();
706
+ return `Attempt ${input.attempt} failed with webhook response ${input.response.status}${statusText ? ` ${statusText}` : ""}.`;
707
+ }
708
+ if (input.error instanceof Error) {
709
+ return `Attempt ${input.attempt} failed: ${input.error.message}`;
710
+ }
711
+ return `Attempt ${input.attempt} failed: ${String(input.error)}`;
712
+ };
713
+ var deliverVoiceIntegrationEvent = async (input) => {
714
+ const previousAttempts = input.event.deliveryAttempts ?? 0;
715
+ const matchesConfiguredTypes = input.webhook.eventTypes === undefined ? true : input.webhook.eventTypes.includes(input.event.type);
716
+ if (!matchesConfiguredTypes) {
717
+ return {
718
+ ...input.event,
719
+ deliveryAttempts: 0,
720
+ deliveryError: undefined,
721
+ deliveryStatus: "skipped"
722
+ };
723
+ }
724
+ const fetchImpl = input.webhook.fetch ?? globalThis.fetch;
725
+ if (typeof fetchImpl !== "function") {
726
+ return {
727
+ ...input.event,
728
+ deliveredTo: input.webhook.url,
729
+ deliveryAttempts: 0,
730
+ deliveryError: "Webhook delivery failed: fetch is not available in this runtime.",
731
+ deliveryStatus: "failed"
732
+ };
733
+ }
734
+ const maxRetries = Math.max(0, input.webhook.retries ?? 0);
735
+ const backoffMs = Math.max(0, input.webhook.backoffMs ?? 250);
736
+ const timeoutMs = Math.max(0, input.webhook.timeoutMs ?? 1e4);
737
+ const body = JSON.stringify({
738
+ createdAt: input.event.createdAt,
739
+ id: input.event.id,
740
+ payload: input.event.payload,
741
+ type: input.event.type
742
+ });
743
+ let lastError = "Webhook delivery failed.";
744
+ for (let attempt = 1;attempt <= maxRetries + 1; attempt += 1) {
745
+ let controller;
746
+ let timeout;
747
+ try {
748
+ const headers = {
749
+ "content-type": "application/json",
750
+ ...input.webhook.headers
751
+ };
752
+ if (input.webhook.signingSecret) {
753
+ const timestamp = String(Date.now());
754
+ headers["x-absolutejs-timestamp"] = timestamp;
755
+ headers["x-absolutejs-signature"] = await signVoiceIntegrationWebhookBody({
756
+ body,
757
+ secret: input.webhook.signingSecret,
758
+ timestamp
759
+ });
760
+ }
761
+ controller = timeoutMs > 0 ? new AbortController : undefined;
762
+ const activeController = controller;
763
+ timeout = activeController && timeoutMs > 0 ? setTimeout(() => activeController.abort(), timeoutMs) : undefined;
764
+ const response = await fetchImpl(input.webhook.url, {
765
+ body,
766
+ headers,
767
+ method: "POST",
768
+ signal: controller?.signal
769
+ });
770
+ if (response.ok) {
771
+ if (timeout) {
772
+ clearTimeout(timeout);
773
+ }
774
+ return {
775
+ ...input.event,
776
+ deliveredAt: Date.now(),
777
+ deliveredTo: input.webhook.url,
778
+ deliveryAttempts: previousAttempts + attempt,
779
+ deliveryError: undefined,
780
+ deliveryStatus: "delivered"
781
+ };
782
+ }
783
+ lastError = createVoiceWebhookDeliveryError({
784
+ attempt,
785
+ error: new Error(`HTTP ${response.status}`),
786
+ response
787
+ });
788
+ } catch (error) {
789
+ lastError = createVoiceWebhookDeliveryError({
790
+ attempt,
791
+ error
792
+ });
793
+ } finally {
794
+ if (timeout) {
795
+ clearTimeout(timeout);
796
+ }
797
+ }
798
+ if (attempt <= maxRetries) {
799
+ await sleep(backoffMs * attempt);
800
+ }
801
+ }
802
+ return {
803
+ ...input.event,
804
+ deliveredTo: input.webhook.url,
805
+ deliveryAttempts: previousAttempts + maxRetries + 1,
806
+ deliveryError: lastError,
807
+ deliveryStatus: "failed"
808
+ };
809
+ };
810
+ var ensureTaskHistory = (task, entry) => ({
811
+ ...task,
812
+ history: [
813
+ ...task.history ?? [],
814
+ {
815
+ ...entry,
816
+ at: entry.at ?? Date.now()
817
+ }
818
+ ],
819
+ updatedAt: Date.now()
820
+ });
821
+ var buildVoiceOpsTaskFromReview = (review) => {
822
+ const createdAt = review.generatedAt ?? Date.now();
823
+ const common = {
824
+ createdAt,
825
+ history: [
826
+ {
827
+ actor: "system",
828
+ at: createdAt,
829
+ detail: review.postCall?.summary,
830
+ type: "created"
831
+ }
832
+ ],
833
+ id: `${review.id}:ops`,
834
+ intakeId: review.id,
835
+ outcome: review.summary.outcome,
836
+ recommendedAction: review.postCall?.recommendedAction ?? "Review the voice artifact and decide the next operator action.",
837
+ reviewId: review.id,
838
+ status: "open",
839
+ target: review.postCall?.target,
840
+ updatedAt: createdAt
841
+ };
842
+ switch (review.summary.outcome) {
843
+ case "voicemail":
844
+ return {
845
+ ...common,
846
+ description: review.postCall?.summary ?? "Caller reached voicemail and needs a callback follow-up.",
847
+ kind: "callback",
848
+ title: review.postCall?.target ? `Call back voicemail from ${review.postCall.target}` : "Call back voicemail lead"
849
+ };
850
+ case "no-answer":
851
+ return {
852
+ ...common,
853
+ description: review.postCall?.summary ?? "Live contact was not established and should be retried.",
854
+ kind: "callback",
855
+ title: "Retry no-answer call"
856
+ };
857
+ case "escalated":
858
+ return {
859
+ ...common,
860
+ description: review.postCall?.summary ?? "The automated path escalated this call for human review.",
861
+ kind: "escalation",
862
+ title: "Review escalated call"
863
+ };
864
+ case "transferred":
865
+ return {
866
+ ...common,
867
+ description: review.postCall?.summary ?? "The call was transferred and should be verified downstream.",
868
+ kind: "transfer-check",
869
+ title: review.postCall?.target ? `Verify transfer to ${review.postCall.target}` : "Verify call transfer"
870
+ };
871
+ case "failed":
872
+ return {
873
+ ...common,
874
+ description: review.postCall?.summary ?? "The call failed and needs operator review before retry.",
875
+ kind: "retry-review",
876
+ title: "Inspect failed call before retry"
877
+ };
878
+ default:
879
+ return null;
880
+ }
881
+ };
882
+ var withVoiceIntegrationEventId = (id, event) => ({
883
+ ...event,
884
+ id
885
+ });
886
+ var withVoiceOpsTaskId = (id, task) => ({
887
+ ...task,
888
+ id
889
+ });
890
+ var DEFAULT_VOICE_OPS_TASK_POLICIES = {
891
+ escalated: {
892
+ dueInMs: 10 * 60000,
893
+ name: "escalation-rapid-response",
894
+ priority: "urgent"
895
+ },
896
+ failed: {
897
+ dueInMs: 15 * 60000,
898
+ name: "failed-call-review",
899
+ priority: "high"
900
+ },
901
+ "no-answer": {
902
+ dueInMs: 2 * 60 * 60000,
903
+ name: "no-answer-retry",
904
+ priority: "normal"
905
+ },
906
+ transferred: {
907
+ dueInMs: 20 * 60000,
908
+ name: "transfer-verification",
909
+ priority: "normal"
910
+ },
911
+ voicemail: {
912
+ dueInMs: 30 * 60000,
913
+ name: "voicemail-callback",
914
+ priority: "high"
915
+ }
916
+ };
917
+ var applyVoiceOpsTaskAssignmentRule = (task, rule, input = {}) => {
918
+ const updatedTask = {
919
+ ...task,
920
+ assignee: rule.assign ?? task.assignee,
921
+ priority: rule.priority ?? task.priority,
922
+ queue: rule.queue ?? task.queue,
923
+ recommendedAction: rule.recommendedAction ?? task.recommendedAction,
924
+ title: rule.title ?? task.title
925
+ };
926
+ return ensureTaskHistory(updatedTask, {
927
+ actor: input.actor ?? "system",
928
+ at: input.at,
929
+ detail: input.detail ?? rule.description ?? (rule.name ? `Applied assignment rule ${rule.name}` : "Applied assignment rule"),
930
+ type: "assigned"
931
+ });
932
+ };
933
+ var applyVoiceOpsTaskPolicy = (task, policy, input = {}) => {
934
+ const at = input.at ?? Date.now();
935
+ const updatedTask = {
936
+ ...task,
937
+ assignee: policy.assignee ?? task.assignee,
938
+ dueAt: typeof policy.dueInMs === "number" ? at + Math.max(0, policy.dueInMs) : task.dueAt,
939
+ policyName: policy.name ?? task.policyName,
940
+ priority: policy.priority ?? task.priority,
941
+ queue: policy.queue ?? task.queue,
942
+ recommendedAction: policy.recommendedAction ?? task.recommendedAction,
943
+ target: policy.target ?? task.target,
944
+ title: policy.title ?? task.title
945
+ };
946
+ return ensureTaskHistory(updatedTask, {
947
+ actor: input.actor ?? "system",
948
+ at,
949
+ detail: input.detail ?? (policy.name ? `Applied ops policy ${policy.name}` : "Applied ops task policy"),
950
+ type: "policy-applied"
951
+ });
952
+ };
953
+ var assignVoiceOpsTask = (task, owner, input = {}) => {
954
+ const normalizedOwner = owner.trim() || "ops";
955
+ return ensureTaskHistory({
956
+ ...task,
957
+ assignee: normalizedOwner
958
+ }, {
959
+ actor: input.actor ?? normalizedOwner,
960
+ at: input.at,
961
+ detail: `Assigned to ${normalizedOwner}`,
962
+ type: "assigned"
963
+ });
964
+ };
965
+ var buildVoiceOpsTaskFromSLABreach = (task, policy = {}) => {
966
+ const createdAt = task.slaBreachedAt ?? Date.now();
967
+ const followUp = withVoiceOpsTaskId(`${task.id}:sla`, {
968
+ assignee: policy.assignee ?? task.assignee,
969
+ createdAt,
970
+ description: policy.description ?? `Task ${task.id} breached its SLA and needs operator follow-up.`,
971
+ dueAt: typeof policy.dueInMs === "number" ? createdAt + Math.max(0, policy.dueInMs) : undefined,
972
+ history: [
973
+ {
974
+ actor: "system",
975
+ at: createdAt,
976
+ detail: policy.name ?? (task.policyName ? `Created from SLA breach on policy ${task.policyName}` : "Created from SLA breach"),
977
+ type: "created"
978
+ }
979
+ ],
980
+ kind: task.kind,
981
+ intakeId: task.intakeId,
982
+ outcome: task.outcome,
983
+ priority: policy.priority ?? "urgent",
984
+ policyName: policy.name ?? `${task.policyName ?? task.kind}:sla`,
985
+ queue: policy.queue ?? task.queue,
986
+ recommendedAction: policy.recommendedAction ?? `Review overdue task ${task.id} and decide the next operator action.`,
987
+ reviewId: task.reviewId,
988
+ status: "open",
989
+ target: task.target,
990
+ title: policy.title ?? `SLA follow-up for ${task.title}`,
991
+ updatedAt: createdAt
992
+ });
993
+ return followUp;
994
+ };
995
+ var claimVoiceOpsTask = (task, workerId, input = {
996
+ leaseMs: 30000
997
+ }) => {
998
+ const at = input.at ?? Date.now();
999
+ const leaseMs = Math.max(1, input.leaseMs);
1000
+ return ensureTaskHistory({
1001
+ ...task,
1002
+ claimExpiresAt: at + leaseMs,
1003
+ claimedAt: at,
1004
+ claimedBy: workerId,
1005
+ status: task.status === "done" ? task.status : "in-progress"
1006
+ }, {
1007
+ actor: input.actor ?? workerId,
1008
+ at,
1009
+ detail: input.detail ?? `Claimed by ${workerId}`,
1010
+ type: "claimed"
1011
+ });
1012
+ };
1013
+ var completeVoiceOpsTask = (task, input = {}) => ensureTaskHistory({
1014
+ ...task,
1015
+ claimExpiresAt: undefined,
1016
+ claimedAt: undefined,
1017
+ claimedBy: undefined,
1018
+ lastProcessedAt: input.at ?? Date.now(),
1019
+ processingError: undefined,
1020
+ status: "done"
1021
+ }, {
1022
+ actor: input.actor ?? task.assignee ?? "ops",
1023
+ at: input.at,
1024
+ detail: input.detail ?? "Marked done",
1025
+ type: "completed"
1026
+ });
1027
+ var createVoiceCallCompletedEvent = (input) => createVoiceIntegrationEvent("call.completed", {
1028
+ call: input.session.call,
1029
+ disposition: input.disposition ?? input.session.call?.disposition,
1030
+ scenarioId: input.session.scenarioId,
1031
+ sessionId: input.session.id,
1032
+ sessionSummary: input.sessionSummary,
1033
+ status: input.session.status,
1034
+ turnCount: input.session.turns.length
1035
+ }, {
1036
+ id: `${input.session.id}:call.completed`
1037
+ });
1038
+ var createVoiceIntegrationEvent = (type, payload, input = {}) => ({
1039
+ createdAt: input.createdAt ?? Date.now(),
1040
+ id: input.id ?? crypto.randomUUID(),
1041
+ payload,
1042
+ type
1043
+ });
1044
+ var createVoiceReviewSavedEvent = (review) => createVoiceIntegrationEvent("review.saved", {
1045
+ elapsedMs: review.summary.elapsedMs,
1046
+ firstTurnLatencyMs: review.summary.firstTurnLatencyMs,
1047
+ outcome: review.summary.outcome,
1048
+ postCall: review.postCall,
1049
+ reviewId: review.id,
1050
+ title: review.title
1051
+ }, {
1052
+ id: `${review.id}:review.saved`
1053
+ });
1054
+ var createVoiceTaskCreatedEvent = (task) => createVoiceIntegrationEvent("task.created", {
1055
+ assignee: task.assignee,
1056
+ dueAt: task.dueAt,
1057
+ kind: task.kind,
1058
+ outcome: task.outcome,
1059
+ priority: task.priority,
1060
+ queue: task.queue,
1061
+ recommendedAction: task.recommendedAction,
1062
+ reviewId: task.reviewId,
1063
+ status: task.status,
1064
+ target: task.target,
1065
+ taskId: task.id,
1066
+ title: task.title
1067
+ }, {
1068
+ id: `${task.id}:task.created:${task.updatedAt}`
1069
+ });
1070
+ var createVoiceTaskSLABreachedEvent = (task) => createVoiceIntegrationEvent("task.sla_breached", {
1071
+ assignee: task.assignee,
1072
+ dueAt: task.dueAt,
1073
+ kind: task.kind,
1074
+ outcome: task.outcome,
1075
+ priority: task.priority,
1076
+ queue: task.queue,
1077
+ recommendedAction: task.recommendedAction,
1078
+ reviewId: task.reviewId,
1079
+ slaBreachedAt: task.slaBreachedAt,
1080
+ status: task.status,
1081
+ target: task.target,
1082
+ taskId: task.id,
1083
+ title: task.title
1084
+ }, {
1085
+ id: `${task.id}:task.sla_breached:${task.slaBreachedAt ?? task.updatedAt}`
1086
+ });
1087
+ var createVoiceTaskUpdatedEvent = (task) => createVoiceIntegrationEvent("task.updated", {
1088
+ assignee: task.assignee,
1089
+ dueAt: task.dueAt,
1090
+ history: task.history,
1091
+ kind: task.kind,
1092
+ outcome: task.outcome,
1093
+ priority: task.priority,
1094
+ queue: task.queue,
1095
+ recommendedAction: task.recommendedAction,
1096
+ reviewId: task.reviewId,
1097
+ slaBreachedAt: task.slaBreachedAt,
1098
+ status: task.status,
1099
+ target: task.target,
1100
+ taskId: task.id,
1101
+ title: task.title,
1102
+ updatedAt: task.updatedAt
1103
+ }, {
1104
+ id: `${task.id}:task.updated:${task.updatedAt}`
1105
+ });
1106
+ var deadLetterVoiceOpsTask = (task, input = {}) => {
1107
+ const at = input.at ?? Date.now();
1108
+ return ensureTaskHistory({
1109
+ ...task,
1110
+ claimExpiresAt: undefined,
1111
+ claimedAt: undefined,
1112
+ claimedBy: undefined,
1113
+ deadLetteredAt: at,
1114
+ lastProcessedAt: at,
1115
+ status: "open"
1116
+ }, {
1117
+ actor: input.actor ?? task.assignee ?? "ops",
1118
+ at,
1119
+ detail: input.detail ?? "Task moved to dead-letter queue",
1120
+ type: "dead-lettered"
1121
+ });
1122
+ };
1123
+ var failVoiceOpsTask = (task, input = {}) => {
1124
+ const at = input.at ?? Date.now();
1125
+ const detail = input.detail ?? input.error ?? "Task processing failed";
1126
+ return ensureTaskHistory({
1127
+ ...task,
1128
+ lastProcessedAt: at,
1129
+ processingAttempts: (task.processingAttempts ?? 0) + 1,
1130
+ processingError: input.error ?? detail,
1131
+ status: task.status === "done" ? task.status : "open"
1132
+ }, {
1133
+ actor: input.actor ?? task.claimedBy ?? task.assignee ?? "ops",
1134
+ at,
1135
+ detail,
1136
+ type: "failed"
1137
+ });
1138
+ };
1139
+ var hasVoiceOpsTaskSLABreach = (task) => typeof task.slaBreachedAt === "number";
1140
+ var heartbeatVoiceOpsTask = (task, workerId, input = {
1141
+ leaseMs: 30000
1142
+ }) => {
1143
+ if (task.claimedBy && task.claimedBy !== workerId) {
1144
+ throw new Error(`Cannot heartbeat task ${task.id}: claimed by ${task.claimedBy}, not ${workerId}.`);
1145
+ }
1146
+ const at = input.at ?? Date.now();
1147
+ const leaseMs = Math.max(1, input.leaseMs);
1148
+ return ensureTaskHistory({
1149
+ ...task,
1150
+ claimExpiresAt: at + leaseMs,
1151
+ claimedAt: task.claimedAt ?? at,
1152
+ claimedBy: workerId
1153
+ }, {
1154
+ actor: input.actor ?? workerId,
1155
+ at,
1156
+ detail: input.detail ?? `Heartbeat from ${workerId}`,
1157
+ type: "heartbeat"
1158
+ });
1159
+ };
1160
+ var isVoiceOpsTaskOverdue = (task, input = {}) => typeof task.dueAt === "number" && task.status !== "done" && task.dueAt <= (input.at ?? Date.now());
1161
+ var listVoiceOpsTasks = (tasks) => [...tasks].sort((left, right) => right.createdAt - left.createdAt);
1162
+ var markVoiceOpsTaskSLABreached = (task, input = {}) => {
1163
+ const at = input.at ?? Date.now();
1164
+ if (hasVoiceOpsTaskSLABreach(task)) {
1165
+ return task;
1166
+ }
1167
+ return ensureTaskHistory({
1168
+ ...task,
1169
+ slaBreachedAt: at
1170
+ }, {
1171
+ actor: input.actor ?? "system",
1172
+ at,
1173
+ detail: input.detail ?? "Task breached its SLA",
1174
+ type: "sla-breached"
1175
+ });
1176
+ };
1177
+ var matchesVoiceOpsTaskAssignmentRule = (task, rule) => {
1178
+ const { when } = rule;
1179
+ if (!when) {
1180
+ return true;
1181
+ }
1182
+ if (when.assignee !== undefined && task.assignee !== when.assignee) {
1183
+ return false;
1184
+ }
1185
+ if (when.kind !== undefined && task.kind !== when.kind) {
1186
+ return false;
1187
+ }
1188
+ if (when.outcome !== undefined && task.outcome !== when.outcome) {
1189
+ return false;
1190
+ }
1191
+ if (when.policyName !== undefined && task.policyName !== when.policyName) {
1192
+ return false;
1193
+ }
1194
+ if (when.priority !== undefined && task.priority !== when.priority) {
1195
+ return false;
1196
+ }
1197
+ if (when.queue !== undefined && task.queue !== when.queue) {
1198
+ return false;
1199
+ }
1200
+ if (when.status !== undefined && task.status !== when.status) {
1201
+ return false;
1202
+ }
1203
+ return true;
1204
+ };
1205
+ var reopenVoiceOpsTask = (task, input = {}) => ensureTaskHistory({
1206
+ ...task,
1207
+ claimExpiresAt: undefined,
1208
+ claimedAt: undefined,
1209
+ claimedBy: undefined,
1210
+ deadLetteredAt: undefined,
1211
+ processingError: undefined,
1212
+ status: "open"
1213
+ }, {
1214
+ actor: input.actor ?? task.assignee ?? "ops",
1215
+ at: input.at,
1216
+ detail: input.detail ?? "Task reopened",
1217
+ type: "reopened"
1218
+ });
1219
+ var requeueVoiceOpsTask = (task, input = {}) => ensureTaskHistory({
1220
+ ...task,
1221
+ claimExpiresAt: undefined,
1222
+ claimedAt: undefined,
1223
+ claimedBy: undefined,
1224
+ processingError: undefined,
1225
+ status: "open"
1226
+ }, {
1227
+ actor: input.actor ?? task.claimedBy ?? task.assignee ?? "ops",
1228
+ at: input.at,
1229
+ detail: input.detail ?? "Task requeued",
1230
+ type: "requeued"
1231
+ });
1232
+ var resolveVoiceOpsTaskAgeBucket = (task, input = {}) => {
1233
+ const at = input.at ?? Date.now();
1234
+ const freshMs = Math.max(0, input.agingMs ?? 30 * 60000);
1235
+ const staleMs = Math.max(freshMs, input.staleMs ?? 4 * 60 * 60000);
1236
+ const dueSoonMs = Math.max(0, input.dueSoonMs ?? 15 * 60000);
1237
+ const ageMs = Math.max(0, at - task.createdAt);
1238
+ if (isVoiceOpsTaskOverdue(task, { at })) {
1239
+ return ageMs >= staleMs ? "stale" : "overdue";
1240
+ }
1241
+ if (typeof task.dueAt === "number" && task.status !== "done" && task.dueAt - at <= dueSoonMs) {
1242
+ return "due-soon";
1243
+ }
1244
+ if (ageMs >= staleMs) {
1245
+ return "stale";
1246
+ }
1247
+ if (ageMs >= freshMs) {
1248
+ return "aging";
1249
+ }
1250
+ return "fresh";
1251
+ };
1252
+ var resolveVoiceOpsTaskAssignment = (input) => input.rules?.find((rule) => matchesVoiceOpsTaskAssignmentRule(input.task, rule));
1253
+ var resolveVoiceOpsTaskPolicy = (input) => {
1254
+ const { disposition } = input;
1255
+ if (!disposition) {
1256
+ return;
1257
+ }
1258
+ const defaultPolicy = DEFAULT_VOICE_OPS_TASK_POLICIES[disposition];
1259
+ const customPolicy = input.policies?.[disposition];
1260
+ if (!defaultPolicy && !customPolicy) {
1261
+ return;
1262
+ }
1263
+ return {
1264
+ ...defaultPolicy,
1265
+ ...customPolicy
1266
+ };
1267
+ };
1268
+ var startVoiceOpsTask = (task, input = {}) => ensureTaskHistory({
1269
+ ...task,
1270
+ status: "in-progress"
1271
+ }, {
1272
+ actor: input.actor ?? task.assignee ?? "ops",
1273
+ at: input.at,
1274
+ detail: input.detail ?? "Work started",
1275
+ type: "started"
1276
+ });
1277
+ var summarizeVoiceOpsTaskAnalytics = (tasks, input = {}) => {
1278
+ const at = input.at ?? Date.now();
1279
+ const agingBuckets = new Map;
1280
+ const assignees = new Map;
1281
+ const workers = new Map;
1282
+ let totalCompleted = 0;
1283
+ let totalOverdue = 0;
1284
+ for (const task of tasks) {
1285
+ const bucket = resolveVoiceOpsTaskAgeBucket(task, { ...input, at });
1286
+ agingBuckets.set(bucket, (agingBuckets.get(bucket) ?? 0) + 1);
1287
+ if (task.assignee) {
1288
+ const assignee = assignees.get(task.assignee) ?? {
1289
+ claimed: 0,
1290
+ completed: 0,
1291
+ completionMsTotal: 0,
1292
+ completionSamples: 0,
1293
+ inProgress: 0,
1294
+ open: 0,
1295
+ overdue: 0,
1296
+ total: 0
1297
+ };
1298
+ assignee.total += 1;
1299
+ if (task.status === "open") {
1300
+ assignee.open += 1;
1301
+ } else if (task.status === "in-progress") {
1302
+ assignee.inProgress += 1;
1303
+ } else if (task.status === "done") {
1304
+ assignee.completed += 1;
1305
+ }
1306
+ if (task.claimedBy && (!task.claimExpiresAt || task.claimExpiresAt > at)) {
1307
+ assignee.claimed += 1;
1308
+ }
1309
+ if (isVoiceOpsTaskOverdue(task, { at })) {
1310
+ assignee.overdue += 1;
1311
+ }
1312
+ const completedAt = task.history.findLast((entry) => entry.type === "completed")?.at;
1313
+ if (task.status === "done" && typeof completedAt === "number") {
1314
+ assignee.completionMsTotal += Math.max(0, completedAt - task.createdAt);
1315
+ assignee.completionSamples += 1;
1316
+ }
1317
+ assignees.set(task.assignee, assignee);
1318
+ }
1319
+ if (isVoiceOpsTaskOverdue(task, { at })) {
1320
+ totalOverdue += 1;
1321
+ }
1322
+ if (task.status === "done") {
1323
+ totalCompleted += 1;
1324
+ }
1325
+ for (const entry of task.history) {
1326
+ if (!["claimed", "heartbeat", "completed", "failed", "requeued"].includes(entry.type)) {
1327
+ continue;
1328
+ }
1329
+ const worker = workers.get(entry.actor) ?? {
1330
+ activeClaims: 0,
1331
+ completed: 0,
1332
+ failed: 0,
1333
+ heartbeats: 0,
1334
+ requeued: 0,
1335
+ totalClaims: 0
1336
+ };
1337
+ if (entry.type === "claimed") {
1338
+ worker.totalClaims += 1;
1339
+ } else if (entry.type === "heartbeat") {
1340
+ worker.heartbeats += 1;
1341
+ } else if (entry.type === "completed") {
1342
+ worker.completed += 1;
1343
+ } else if (entry.type === "failed") {
1344
+ worker.failed += 1;
1345
+ } else if (entry.type === "requeued") {
1346
+ worker.requeued += 1;
1347
+ }
1348
+ workers.set(entry.actor, worker);
1349
+ }
1350
+ if (task.claimedBy && (!task.claimExpiresAt || task.claimExpiresAt > at)) {
1351
+ const worker = workers.get(task.claimedBy) ?? {
1352
+ activeClaims: 0,
1353
+ completed: 0,
1354
+ failed: 0,
1355
+ heartbeats: 0,
1356
+ requeued: 0,
1357
+ totalClaims: 0
1358
+ };
1359
+ worker.activeClaims += 1;
1360
+ workers.set(task.claimedBy, worker);
1361
+ }
1362
+ }
1363
+ return {
1364
+ agingBuckets: [...agingBuckets.entries()].sort((left, right) => right[1] - left[1]),
1365
+ assignees: [...assignees.entries()].map(([assignee, value]) => ({
1366
+ assignee,
1367
+ averageCompletionMs: value.completionSamples > 0 ? value.completionMsTotal / value.completionSamples : undefined,
1368
+ claimed: value.claimed,
1369
+ completed: value.completed,
1370
+ inProgress: value.inProgress,
1371
+ open: value.open,
1372
+ overdue: value.overdue,
1373
+ total: value.total
1374
+ })).sort((left, right) => right.total - left.total),
1375
+ totalCompleted,
1376
+ totalOverdue,
1377
+ totalTasks: tasks.length,
1378
+ workers: [...workers.entries()].map(([workerId, value]) => ({
1379
+ activeClaims: value.activeClaims,
1380
+ completed: value.completed,
1381
+ failed: value.failed,
1382
+ heartbeats: value.heartbeats,
1383
+ requeued: value.requeued,
1384
+ totalClaims: value.totalClaims,
1385
+ workerId
1386
+ })).sort((left, right) => right.totalClaims - left.totalClaims)
1387
+ };
1388
+ };
1389
+ var summarizeVoiceOpsTasks = (tasks) => {
1390
+ const summary = {
1391
+ byClaimedBy: new Map,
1392
+ byKind: new Map,
1393
+ byOutcome: new Map,
1394
+ byPriority: new Map,
1395
+ byQueue: new Map,
1396
+ claimed: 0,
1397
+ done: 0,
1398
+ inProgress: 0,
1399
+ open: 0,
1400
+ overdue: 0,
1401
+ topAssignees: new Map,
1402
+ topQueues: new Map,
1403
+ topTargets: new Map,
1404
+ total: tasks.length
1405
+ };
1406
+ for (const task of tasks) {
1407
+ if (task.status === "open") {
1408
+ summary.open += 1;
1409
+ } else if (task.status === "in-progress") {
1410
+ summary.inProgress += 1;
1411
+ } else if (task.status === "done") {
1412
+ summary.done += 1;
1413
+ }
1414
+ if (task.claimedBy && (!task.claimExpiresAt || task.claimExpiresAt > Date.now())) {
1415
+ summary.claimed += 1;
1416
+ summary.byClaimedBy.set(task.claimedBy, (summary.byClaimedBy.get(task.claimedBy) ?? 0) + 1);
1417
+ }
1418
+ summary.byKind.set(task.kind, (summary.byKind.get(task.kind) ?? 0) + 1);
1419
+ if (task.outcome) {
1420
+ summary.byOutcome.set(task.outcome, (summary.byOutcome.get(task.outcome) ?? 0) + 1);
1421
+ }
1422
+ if (task.target) {
1423
+ summary.topTargets.set(task.target, (summary.topTargets.get(task.target) ?? 0) + 1);
1424
+ }
1425
+ if (task.assignee) {
1426
+ summary.topAssignees.set(task.assignee, (summary.topAssignees.get(task.assignee) ?? 0) + 1);
1427
+ }
1428
+ if (task.priority) {
1429
+ summary.byPriority.set(task.priority, (summary.byPriority.get(task.priority) ?? 0) + 1);
1430
+ }
1431
+ if (task.queue) {
1432
+ summary.byQueue.set(task.queue, (summary.byQueue.get(task.queue) ?? 0) + 1);
1433
+ summary.topQueues.set(task.queue, (summary.topQueues.get(task.queue) ?? 0) + 1);
1434
+ }
1435
+ if (isVoiceOpsTaskOverdue(task)) {
1436
+ summary.overdue += 1;
1437
+ }
1438
+ }
1439
+ return {
1440
+ byClaimedBy: [...summary.byClaimedBy.entries()].sort((left, right) => right[1] - left[1]),
1441
+ byKind: [...summary.byKind.entries()].sort((left, right) => right[1] - left[1]),
1442
+ byOutcome: [...summary.byOutcome.entries()].sort((left, right) => right[1] - left[1]),
1443
+ byPriority: [...summary.byPriority.entries()].sort((left, right) => right[1] - left[1]),
1444
+ byQueue: [...summary.byQueue.entries()].sort((left, right) => right[1] - left[1]),
1445
+ claimed: summary.claimed,
1446
+ done: summary.done,
1447
+ inProgress: summary.inProgress,
1448
+ open: summary.open,
1449
+ overdue: summary.overdue,
1450
+ topAssignees: [...summary.topAssignees.entries()].sort((left, right) => right[1] - left[1]),
1451
+ topQueues: [...summary.topQueues.entries()].sort((left, right) => right[1] - left[1]),
1452
+ topTargets: [...summary.topTargets.entries()].sort((left, right) => right[1] - left[1]),
1453
+ total: summary.total
1454
+ };
1455
+ };
1456
+
1457
+ // src/core/store.ts
1458
+ var createId = () => crypto.randomUUID();
1459
+ var createVoiceSessionRecord = (id, scenarioId) => ({
1460
+ committedTurnIds: [],
1461
+ createdAt: Date.now(),
1462
+ currentTurn: {
1463
+ finalText: "",
1464
+ lastSpeechAt: undefined,
1465
+ lastTranscriptAt: undefined,
1466
+ partialEndedAt: undefined,
1467
+ partialStartedAt: undefined,
1468
+ partialText: "",
1469
+ silenceStartedAt: undefined,
1470
+ transcripts: []
1471
+ },
1472
+ id,
1473
+ scenarioId,
1474
+ reconnect: { attempts: 0 },
1475
+ status: "active",
1476
+ transcripts: [],
1477
+ turns: [],
1478
+ lastCommittedTurn: {
1479
+ committedAt: 0,
1480
+ signature: "",
1481
+ text: "",
1482
+ transcriptIds: []
1483
+ }
1484
+ });
1485
+ var resetVoiceSessionRecord = (id, existing, scenarioId) => ({
1486
+ ...createVoiceSessionRecord(id, scenarioId),
1487
+ metadata: existing?.metadata
1488
+ });
1489
+ var toVoiceSessionSummary = (session) => ({
1490
+ createdAt: session.createdAt,
1491
+ id: session.id,
1492
+ lastActivityAt: session.lastActivityAt,
1493
+ status: session.status,
1494
+ turnCount: session.turns.length
1495
+ });
1496
+
1497
+ // src/internal/html.ts
1498
+ var escapeHtml = (value) => String(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1499
+
1500
+ // src/core/trace.ts
1501
+ var createVoiceTraceEvent = (event) => ({
1502
+ ...event,
1503
+ at: event.at,
1504
+ id: event.id ?? createVoiceTraceEventId({
1505
+ at: event.at,
1506
+ sessionId: event.sessionId,
1507
+ turnId: event.turnId,
1508
+ type: event.type
1509
+ })
1510
+ });
1511
+ var createVoiceTraceEventId = (event) => [
1512
+ event.sessionId,
1513
+ event.turnId ?? "session",
1514
+ event.type,
1515
+ String(event.at ?? Date.now()),
1516
+ crypto.randomUUID()
1517
+ ].map(encodeURIComponent).join(":");
1518
+ var createVoiceTraceSinkDeliveryId = (events) => {
1519
+ const firstEvent = events[0];
1520
+ return [
1521
+ firstEvent?.sessionId ?? "trace",
1522
+ firstEvent?.traceId ?? "sink",
1523
+ String(firstEvent?.at ?? Date.now()),
1524
+ crypto.randomUUID()
1525
+ ].map(encodeURIComponent).join(":");
1526
+ };
1527
+ var createVoiceTraceSinkDeliveryRecord = (input) => {
1528
+ const createdAt = input.createdAt ?? Date.now();
1529
+ return {
1530
+ createdAt,
1531
+ deliveredAt: input.deliveredAt,
1532
+ deliveryAttempts: input.deliveryAttempts,
1533
+ deliveryError: input.deliveryError,
1534
+ deliveryStatus: input.deliveryStatus ?? "pending",
1535
+ events: input.events,
1536
+ id: input.id ?? createVoiceTraceSinkDeliveryId(input.events),
1537
+ sinkDeliveries: input.sinkDeliveries,
1538
+ updatedAt: input.updatedAt ?? createdAt
1539
+ };
1540
+ };
1541
+ var matchesTraceFilter = (event, filter) => {
1542
+ if (filter.sessionId !== undefined && event.sessionId !== filter.sessionId) {
1543
+ return false;
1544
+ }
1545
+ if (filter.turnId !== undefined && event.turnId !== filter.turnId) {
1546
+ return false;
1547
+ }
1548
+ if (filter.scenarioId !== undefined && event.scenarioId !== filter.scenarioId) {
1549
+ return false;
1550
+ }
1551
+ if (filter.traceId !== undefined && event.traceId !== filter.traceId) {
1552
+ return false;
1553
+ }
1554
+ if (filter.type !== undefined) {
1555
+ const types = Array.isArray(filter.type) ? filter.type : [filter.type];
1556
+ if (!types.includes(event.type)) {
1557
+ return false;
1558
+ }
1559
+ }
1560
+ return true;
1561
+ };
1562
+ var createVoiceProofTraceStore = (options = {}) => {
1563
+ const proofStore = options.proofStore ?? createVoiceMemoryTraceEventStore();
1564
+ const scopedProofStore = options.scope ? createVoiceScopedTraceEventStore(proofStore, options.scope) : proofStore;
1565
+ return {
1566
+ append: async (event) => {
1567
+ const stored = await proofStore.append(event);
1568
+ await options.mirrorStore?.append(stored);
1569
+ return stored;
1570
+ },
1571
+ get: async (id) => await proofStore.get(id) ?? await options.mirrorStore?.get(id),
1572
+ list: (filter) => scopedProofStore.list(filter),
1573
+ remove: async (id) => {
1574
+ await Promise.all([
1575
+ proofStore.remove(id),
1576
+ options.mirrorStore?.remove(id) ?? Promise.resolve()
1577
+ ]);
1578
+ }
1579
+ };
1580
+ };
1581
+ var createVoiceScopedTraceEventStore = (store, scope) => {
1582
+ const upstreamFilter = (filter = {}) => {
1583
+ const next = { ...filter };
1584
+ delete next.limit;
1585
+ if (scope.scenarioId !== undefined) {
1586
+ delete next.scenarioId;
1587
+ }
1588
+ if (scope.sessionId !== undefined) {
1589
+ delete next.sessionId;
1590
+ }
1591
+ if (scope.traceId !== undefined) {
1592
+ delete next.traceId;
1593
+ }
1594
+ if (scope.turnId !== undefined) {
1595
+ delete next.turnId;
1596
+ }
1597
+ if (scope.type !== undefined) {
1598
+ delete next.type;
1599
+ }
1600
+ return next;
1601
+ };
1602
+ const scopedFilter = (filter = {}) => ({
1603
+ ...filter,
1604
+ ...scope
1605
+ });
1606
+ return {
1607
+ append: (event) => store.append(event),
1608
+ get: (id) => store.get(id),
1609
+ list: async (filter) => filterVoiceTraceEvents(await store.list(upstreamFilter(filter)), scopedFilter(filter)),
1610
+ remove: (id) => store.remove(id)
1611
+ };
1612
+ };
1613
+ var filterVoiceTraceEvents = (events, filter = {}) => {
1614
+ const sorted = events.filter((event) => matchesTraceFilter(event, filter)).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
1615
+ return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
1616
+ };
1617
+ var isPruneTimeMatch = (event, options) => {
1618
+ if (typeof options.before === "number" && event.at >= options.before) {
1619
+ return false;
1620
+ }
1621
+ if (typeof options.beforeOrAt === "number" && event.at > options.beforeOrAt) {
1622
+ return false;
1623
+ }
1624
+ return true;
1625
+ };
1626
+ var pruneVoiceTraceEvents = async (options) => {
1627
+ const events = await options.store.list(options.filter);
1628
+ const deleted = selectVoiceTraceEventsForPrune(events, options);
1629
+ if (!options.dryRun) {
1630
+ await Promise.all(deleted.map((event) => options.store.remove(event.id)));
1631
+ }
1632
+ return {
1633
+ deleted,
1634
+ deletedCount: deleted.length,
1635
+ dryRun: Boolean(options.dryRun),
1636
+ scannedCount: events.length
1637
+ };
1638
+ };
1639
+ var selectVoiceTraceEventsForPrune = (events, options = {}) => {
1640
+ let candidates = filterVoiceTraceEvents(events, options.filter).filter((event) => isPruneTimeMatch(event, options));
1641
+ if (typeof options.keepNewest === "number" && options.keepNewest >= 0) {
1642
+ 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));
1643
+ candidates = candidates.filter((event) => !newestIds.has(event.id));
1644
+ }
1645
+ return typeof options.limit === "number" && options.limit >= 0 ? candidates.slice(0, options.limit) : candidates;
1646
+ };
1647
+ var sleep2 = async (delayMs) => {
1648
+ if (delayMs <= 0) {
1649
+ return;
1650
+ }
1651
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1652
+ };
1653
+ var toHex2 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
1654
+ var signVoiceTraceSinkBody = async (input) => {
1655
+ const encoder = new TextEncoder;
1656
+ const key = await crypto.subtle.importKey("raw", encoder.encode(input.secret), {
1657
+ hash: "SHA-256",
1658
+ name: "HMAC"
1659
+ }, false, ["sign"]);
1660
+ const payload = encoder.encode(`${input.timestamp}.${input.body}`);
1661
+ const signature = await crypto.subtle.sign("HMAC", key, payload);
1662
+ return `sha256=${toHex2(new Uint8Array(signature))}`;
1663
+ };
1664
+ var createVoiceTraceSinkDeliveryError = (input) => {
1665
+ if (input.response) {
1666
+ const statusText = input.response.statusText?.trim();
1667
+ return `Attempt ${input.attempt} failed with trace sink response ${input.response.status}${statusText ? ` ${statusText}` : ""}.`;
1668
+ }
1669
+ if (input.error instanceof Error) {
1670
+ return `Attempt ${input.attempt} failed: ${input.error.message}`;
1671
+ }
1672
+ return `Attempt ${input.attempt} failed: ${String(input.error)}`;
1673
+ };
1674
+ var normalizeVoiceTraceS3KeyPrefix = (prefix) => prefix?.trim().replace(/^\/+|\/+$/g, "") ?? "voice/trace-deliveries";
1675
+ var createVoiceTraceS3ObjectKey = (prefix, events) => {
1676
+ const firstEvent = events[0];
1677
+ const safeSessionId = encodeURIComponent(firstEvent?.sessionId ?? "trace");
1678
+ const safeEventId = encodeURIComponent(firstEvent?.id ?? crypto.randomUUID());
1679
+ return `${prefix}/${safeSessionId}/${Date.now()}-${safeEventId}.json`;
1680
+ };
1681
+ var resolveVoiceS3DeliveredTo = (options, key) => {
1682
+ const { bucket } = options;
1683
+ return bucket ? `s3://${bucket}/${key}` : `s3://${key}`;
1684
+ };
1685
+ var aggregateVoiceTraceSinkDeliveryStatus = (deliveries) => {
1686
+ const statuses = Object.values(deliveries).map((delivery) => delivery.status);
1687
+ if (statuses.length === 0 || statuses.every((status) => status === "skipped")) {
1688
+ return "skipped";
1689
+ }
1690
+ if (statuses.some((status) => status === "failed")) {
1691
+ return "failed";
1692
+ }
1693
+ return "delivered";
1694
+ };
1695
+ var createVoiceTraceHTTPSink = (options) => ({
1696
+ eventTypes: options.eventTypes,
1697
+ id: options.id,
1698
+ kind: options.kind ?? "http",
1699
+ deliver: async ({ events }) => {
1700
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1701
+ if (typeof fetchImpl !== "function") {
1702
+ return {
1703
+ attempts: 0,
1704
+ deliveredTo: options.url,
1705
+ error: "Trace sink delivery failed: fetch is not available in this runtime.",
1706
+ eventCount: events.length,
1707
+ status: "failed"
1708
+ };
1709
+ }
1710
+ const maxRetries = Math.max(0, options.retries ?? 0);
1711
+ const backoffMs = Math.max(0, options.backoffMs ?? 250);
1712
+ const timeoutMs = Math.max(0, options.timeoutMs ?? 1e4);
1713
+ const payload = options.body ? await options.body({ events }) : {
1714
+ eventCount: events.length,
1715
+ events,
1716
+ source: "absolutejs-voice"
1717
+ };
1718
+ const body = JSON.stringify(payload);
1719
+ let lastError = "Trace sink delivery failed.";
1720
+ for (let attempt = 1;attempt <= maxRetries + 1; attempt += 1) {
1721
+ let controller;
1722
+ let timeout;
1723
+ try {
1724
+ const headers = {
1725
+ "content-type": "application/json",
1726
+ ...options.headers
1727
+ };
1728
+ if (options.signingSecret) {
1729
+ const timestamp = String(Date.now());
1730
+ headers["x-absolutejs-timestamp"] = timestamp;
1731
+ headers["x-absolutejs-signature"] = await signVoiceTraceSinkBody({
1732
+ body,
1733
+ secret: options.signingSecret,
1734
+ timestamp
1735
+ });
1736
+ }
1737
+ controller = timeoutMs > 0 ? new AbortController : undefined;
1738
+ if (controller && timeoutMs > 0) {
1739
+ timeout = setTimeout(() => controller?.abort(), timeoutMs);
1740
+ }
1741
+ const response = await fetchImpl(options.url, {
1742
+ body,
1743
+ headers,
1744
+ method: options.method ?? "POST",
1745
+ signal: controller?.signal
1746
+ });
1747
+ if (response.ok) {
1748
+ let responseBody;
1749
+ try {
1750
+ responseBody = await response.clone().json();
1751
+ } catch {
1752
+ responseBody = undefined;
1753
+ }
1754
+ return {
1755
+ attempts: attempt,
1756
+ deliveredAt: Date.now(),
1757
+ deliveredTo: options.url,
1758
+ eventCount: events.length,
1759
+ responseBody,
1760
+ status: "delivered"
1761
+ };
1762
+ }
1763
+ lastError = createVoiceTraceSinkDeliveryError({
1764
+ attempt,
1765
+ response
1766
+ });
1767
+ } catch (error) {
1768
+ lastError = createVoiceTraceSinkDeliveryError({
1769
+ attempt,
1770
+ error
1771
+ });
1772
+ } finally {
1773
+ if (timeout) {
1774
+ clearTimeout(timeout);
1775
+ }
1776
+ }
1777
+ if (attempt <= maxRetries) {
1778
+ await sleep2(backoffMs * attempt);
1779
+ }
1780
+ }
1781
+ return {
1782
+ attempts: maxRetries + 1,
1783
+ deliveredTo: options.url,
1784
+ error: lastError,
1785
+ eventCount: events.length,
1786
+ status: "failed"
1787
+ };
1788
+ }
1789
+ });
1790
+ var createVoiceTraceS3Sink = (options) => {
1791
+ const client = options.client ?? new Bun.S3Client(options);
1792
+ const keyPrefix = normalizeVoiceTraceS3KeyPrefix(options.keyPrefix);
1793
+ return {
1794
+ eventTypes: options.eventTypes,
1795
+ id: options.id,
1796
+ kind: options.kind ?? "s3",
1797
+ deliver: async ({ events }) => {
1798
+ const key = createVoiceTraceS3ObjectKey(keyPrefix, events);
1799
+ const payload = options.body ? await options.body({ events, key }) : {
1800
+ eventCount: events.length,
1801
+ events,
1802
+ key,
1803
+ source: "absolutejs-voice"
1804
+ };
1805
+ try {
1806
+ const file = client.file(key, options);
1807
+ await file.write(JSON.stringify(payload), {
1808
+ type: options.contentType ?? "application/json"
1809
+ });
1810
+ return {
1811
+ attempts: 1,
1812
+ deliveredAt: Date.now(),
1813
+ deliveredTo: resolveVoiceS3DeliveredTo(options, key),
1814
+ eventCount: events.length,
1815
+ responseBody: { key },
1816
+ status: "delivered"
1817
+ };
1818
+ } catch (error) {
1819
+ return {
1820
+ attempts: 1,
1821
+ deliveredTo: resolveVoiceS3DeliveredTo(options, key),
1822
+ error: error instanceof Error ? error.message : String(error),
1823
+ eventCount: events.length,
1824
+ status: "failed"
1825
+ };
1826
+ }
1827
+ }
1828
+ };
1829
+ };
1830
+ var createVoiceTraceSinkStore = (options) => {
1831
+ const deliver = async (event) => {
1832
+ const result = await deliverVoiceTraceEventsToSinks({
1833
+ events: [event],
1834
+ redact: options.redact,
1835
+ sinks: options.sinks
1836
+ });
1837
+ await options.onDelivery?.(result);
1838
+ };
1839
+ return {
1840
+ append: async (event) => {
1841
+ const stored = await options.store.append(event);
1842
+ if (options.deliveryQueue) {
1843
+ const delivery2 = createVoiceTraceSinkDeliveryRecord({
1844
+ events: [stored]
1845
+ });
1846
+ await options.deliveryQueue.set(delivery2.id, delivery2);
1847
+ return stored;
1848
+ }
1849
+ const delivery = deliver(stored);
1850
+ if (options.awaitDelivery) {
1851
+ await delivery;
1852
+ } else {
1853
+ delivery.catch((error) => {
1854
+ options.onError?.(error);
1855
+ });
1856
+ }
1857
+ return stored;
1858
+ },
1859
+ get: (id) => options.store.get(id),
1860
+ list: (filter) => options.store.list(filter),
1861
+ remove: (id) => options.store.remove(id)
1862
+ };
1863
+ };
1864
+ var deliverVoiceTraceEventsToSinks = async (input) => {
1865
+ const events = input.redact ? redactVoiceTraceEvents(input.events, input.redact) : input.events;
1866
+ const sinkDeliveries = {};
1867
+ for (const sink of input.sinks) {
1868
+ const sinkEvents = sink.eventTypes?.length ? events.filter((event) => sink.eventTypes?.includes(event.type)) : events;
1869
+ if (sinkEvents.length === 0) {
1870
+ sinkDeliveries[sink.id] = {
1871
+ attempts: 0,
1872
+ eventCount: 0,
1873
+ status: "skipped"
1874
+ };
1875
+ continue;
1876
+ }
1877
+ try {
1878
+ sinkDeliveries[sink.id] = await sink.deliver({
1879
+ events: sinkEvents
1880
+ });
1881
+ } catch (error) {
1882
+ sinkDeliveries[sink.id] = {
1883
+ attempts: 1,
1884
+ error: error instanceof Error ? error.message : String(error),
1885
+ eventCount: sinkEvents.length,
1886
+ status: "failed"
1887
+ };
1888
+ }
1889
+ }
1890
+ return {
1891
+ deliveredAt: Date.now(),
1892
+ eventCount: events.length,
1893
+ sinkDeliveries,
1894
+ status: aggregateVoiceTraceSinkDeliveryStatus(sinkDeliveries)
1895
+ };
1896
+ };
1897
+ var normalizeVoiceProfileTraceTaggerProfile = (profile) => typeof profile === "string" ? { id: profile } : profile?.id ? profile : undefined;
1898
+ var createVoiceMemoryTraceEventStore = () => {
1899
+ const events = new Map;
1900
+ const append = async (event) => {
1901
+ const stored = createVoiceTraceEvent(event);
1902
+ events.set(stored.id, stored);
1903
+ return stored;
1904
+ };
1905
+ const get = async (id) => events.get(id);
1906
+ const list = async (filter) => filterVoiceTraceEvents([...events.values()], filter);
1907
+ const remove = async (id) => {
1908
+ events.delete(id);
1909
+ };
1910
+ return { append, get, list, remove };
1911
+ };
1912
+ var createVoiceMemoryTraceSinkDeliveryStore = () => {
1913
+ const deliveries = new Map;
1914
+ return {
1915
+ get: async (id) => deliveries.get(id),
1916
+ list: async () => [...deliveries.values()].sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id)),
1917
+ remove: async (id) => {
1918
+ deliveries.delete(id);
1919
+ },
1920
+ set: async (id, delivery) => {
1921
+ deliveries.set(id, delivery);
1922
+ }
1923
+ };
1924
+ };
1925
+ var createVoiceProfileTraceTagger = (options) => {
1926
+ const profiles = new Map((options.profiles ?? []).map((profile) => [profile.id, profile]));
1927
+ const defaultProfile = normalizeVoiceProfileTraceTaggerProfile(options.defaultProfile);
1928
+ const resolveProfile = async (event) => {
1929
+ const resolved = normalizeVoiceProfileTraceTaggerProfile(await options.resolveProfile?.(event));
1930
+ const profile = resolved ?? defaultProfile;
1931
+ return profile ? profiles.get(profile.id) ?? profile : undefined;
1932
+ };
1933
+ return {
1934
+ append: async (event) => {
1935
+ const profile = await resolveProfile(event);
1936
+ if (!profile) {
1937
+ return options.store.append(event);
1938
+ }
1939
+ const metadata = {
1940
+ ...event.metadata ?? {},
1941
+ benchmarkProfileId: profile.id,
1942
+ profileDescription: event.metadata?.profileDescription ?? profile.description,
1943
+ profileId: profile.id,
1944
+ profileLabel: event.metadata?.profileLabel ?? profile.label
1945
+ };
1946
+ const payload = event.payload && typeof event.payload === "object" ? {
1947
+ ...event.payload,
1948
+ benchmarkProfileId: event.payload.benchmarkProfileId ?? profile.id,
1949
+ profileDescription: event.payload.profileDescription ?? profile.description,
1950
+ profileId: event.payload.profileId ?? profile.id,
1951
+ profileLabel: event.payload.profileLabel ?? profile.label
1952
+ } : event.payload;
1953
+ return options.store.append({
1954
+ ...event,
1955
+ metadata,
1956
+ payload
1957
+ });
1958
+ },
1959
+ get: (id) => options.store.get(id),
1960
+ list: (filter) => options.store.list(filter),
1961
+ remove: (id) => options.store.remove(id)
1962
+ };
1963
+ };
1964
+ var exportVoiceTrace = async (input) => {
1965
+ const events = await input.store.list(input.filter);
1966
+ return {
1967
+ exportedAt: Date.now(),
1968
+ events: input.redact ? redactVoiceTraceEvents(events, input.redact) : events,
1969
+ filter: input.filter,
1970
+ redacted: Boolean(input.redact)
1971
+ };
1972
+ };
1973
+ var toNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
1974
+ var formatTraceValue = (value) => {
1975
+ if (value === undefined || value === null) {
1976
+ return "";
1977
+ }
1978
+ if (typeof value === "string") {
1979
+ return value;
1980
+ }
1981
+ if (typeof value === "number" || typeof value === "boolean") {
1982
+ return String(value);
1983
+ }
1984
+ try {
1985
+ return JSON.stringify(value);
1986
+ } catch {
1987
+ return String(value);
1988
+ }
1989
+ };
1990
+ var DEFAULT_REDACTION_KEYS = [
1991
+ "apiKey",
1992
+ "authorization",
1993
+ "creditCard",
1994
+ "email",
1995
+ "externalId",
1996
+ "password",
1997
+ "phone",
1998
+ "secret",
1999
+ "ssn",
2000
+ "token"
2001
+ ];
2002
+ var DEFAULT_REDACTION_TEXT_KEYS = [
2003
+ "assistantText",
2004
+ "content",
2005
+ "error",
2006
+ "reason",
2007
+ "summary",
2008
+ "text"
2009
+ ];
2010
+ var EMAIL_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
2011
+ var PHONE_PATTERN = /(?<!\d)(?:\+?1[\s.-]?)?(?:\(?\d{3}\)?[\s.-]?)\d{3}[\s.-]?\d{4}(?!\d)/g;
2012
+ var normalizeRedactionKey = (key) => key.trim().toLowerCase().replace(/[^a-z0-9]/g, "");
2013
+ var resolveVoiceTraceRedactionOptions = (options = {}) => ({
2014
+ keys: typeof options === "boolean" ? DEFAULT_REDACTION_KEYS : options.keys ?? DEFAULT_REDACTION_KEYS,
2015
+ redactEmails: typeof options === "boolean" ? true : options.redactEmails ?? true,
2016
+ redactPhoneNumbers: typeof options === "boolean" ? true : options.redactPhoneNumbers ?? true,
2017
+ redactText: typeof options === "boolean" ? true : options.redactText ?? true,
2018
+ replacement: typeof options === "boolean" ? "[redacted]" : options.replacement ?? "[redacted]",
2019
+ textKeys: typeof options === "boolean" ? DEFAULT_REDACTION_TEXT_KEYS : options.textKeys ?? DEFAULT_REDACTION_TEXT_KEYS
2020
+ });
2021
+ var resolveReplacement = (input) => typeof input.options.replacement === "function" ? input.options.replacement({
2022
+ key: input.key,
2023
+ path: input.path,
2024
+ value: input.value
2025
+ }) : input.options.replacement;
2026
+ var redactVoiceTraceText = (value, options = {}, input = {}) => {
2027
+ const resolved = resolveVoiceTraceRedactionOptions(options);
2028
+ let redacted = value;
2029
+ const replacement = resolveReplacement({
2030
+ key: input.key,
2031
+ options: resolved,
2032
+ path: input.path ?? [],
2033
+ value
2034
+ });
2035
+ if (resolved.redactEmails) {
2036
+ redacted = redacted.replace(EMAIL_PATTERN, replacement);
2037
+ }
2038
+ if (resolved.redactPhoneNumbers) {
2039
+ redacted = redacted.replace(PHONE_PATTERN, replacement);
2040
+ }
2041
+ return redacted;
2042
+ };
2043
+ var redactTraceValue = (value, options, path) => {
2044
+ const key = path.at(-1);
2045
+ const normalizedKey = key ? normalizeRedactionKey(key) : undefined;
2046
+ const sensitiveKeys = new Set(options.keys.map(normalizeRedactionKey));
2047
+ const textKeys = new Set(options.textKeys.map(normalizeRedactionKey));
2048
+ if (normalizedKey && sensitiveKeys.has(normalizedKey) && (value === null || ["boolean", "number", "string", "undefined"].includes(typeof value))) {
2049
+ return resolveReplacement({
2050
+ key,
2051
+ options,
2052
+ path,
2053
+ value: String(value ?? "")
2054
+ });
2055
+ }
2056
+ if (typeof value === "string") {
2057
+ const shouldRedactText = options.redactText && (!normalizedKey || textKeys.has(normalizedKey) || path.length === 0);
2058
+ return shouldRedactText ? redactVoiceTraceText(value, options, {
2059
+ key,
2060
+ path
2061
+ }) : value;
2062
+ }
2063
+ if (Array.isArray(value)) {
2064
+ return value.map((item, index) => redactTraceValue(item, options, [...path, String(index)]));
2065
+ }
2066
+ if (typeof value === "object" && value) {
2067
+ return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [
2068
+ entryKey,
2069
+ redactTraceValue(entryValue, options, [...path, entryKey])
2070
+ ]));
2071
+ }
2072
+ return value;
2073
+ };
2074
+ var evaluateVoiceTrace = (events, options = {}) => {
2075
+ const summary = summarizeVoiceTrace(events);
2076
+ const issues = [];
2077
+ const maxHandoffs = options.maxHandoffs ?? 3;
2078
+ const maxToolErrors = options.maxToolErrors ?? 0;
2079
+ const maxModelCallsPerTurn = options.maxModelCallsPerTurn ?? 6;
2080
+ const turnCountForRatio = Math.max(1, summary.turnCount);
2081
+ if (options.requireCompletedCall !== false && !summary.endedAt) {
2082
+ issues.push({
2083
+ code: "call-not-ended",
2084
+ message: "Trace does not include a call end lifecycle event.",
2085
+ severity: "warning"
2086
+ });
2087
+ }
2088
+ if (summary.failed) {
2089
+ issues.push({
2090
+ code: "session-error",
2091
+ message: "Trace contains a session error or failed call disposition.",
2092
+ severity: "error"
2093
+ });
2094
+ }
2095
+ if (options.requireTranscript !== false && summary.transcriptCount === 0) {
2096
+ issues.push({
2097
+ code: "missing-transcript",
2098
+ message: "Trace does not include any transcript events.",
2099
+ severity: "error"
2100
+ });
2101
+ }
2102
+ if (options.requireTurn !== false && summary.turnCount === 0) {
2103
+ issues.push({
2104
+ code: "missing-turn",
2105
+ message: "Trace does not include any committed turns.",
2106
+ severity: "error"
2107
+ });
2108
+ }
2109
+ if (options.requireAssistantReply !== false && summary.turnCount > 0 && summary.assistantReplyCount === 0) {
2110
+ issues.push({
2111
+ code: "missing-assistant-reply",
2112
+ message: "Trace has committed turns but no assistant replies.",
2113
+ severity: "warning"
2114
+ });
2115
+ }
2116
+ if (summary.toolErrorCount > maxToolErrors) {
2117
+ issues.push({
2118
+ code: "tool-errors",
2119
+ message: `Trace has ${summary.toolErrorCount} tool error(s), above the allowed ${maxToolErrors}.`,
2120
+ severity: "error"
2121
+ });
2122
+ }
2123
+ if (summary.handoffCount > maxHandoffs) {
2124
+ issues.push({
2125
+ code: "too-many-handoffs",
2126
+ message: `Trace has ${summary.handoffCount} handoff(s), above the allowed ${maxHandoffs}.`,
2127
+ severity: "warning"
2128
+ });
2129
+ }
2130
+ if (summary.modelCallCount / turnCountForRatio > maxModelCallsPerTurn) {
2131
+ issues.push({
2132
+ code: "too-many-model-calls",
2133
+ message: `Trace averages more than ${maxModelCallsPerTurn} model calls per committed turn.`,
2134
+ severity: "warning"
2135
+ });
2136
+ }
2137
+ return {
2138
+ issues,
2139
+ pass: !issues.some((issue) => issue.severity === "error"),
2140
+ summary
2141
+ };
2142
+ };
2143
+ var redactVoiceTraceEvent = (event, options = {}) => {
2144
+ const resolved = resolveVoiceTraceRedactionOptions(options);
2145
+ return {
2146
+ ...event,
2147
+ metadata: redactTraceValue(event.metadata, resolved, [
2148
+ "metadata"
2149
+ ]),
2150
+ payload: redactTraceValue(event.payload, resolved, [
2151
+ "payload"
2152
+ ])
2153
+ };
2154
+ };
2155
+ var redactVoiceTraceEvents = (events, options = {}) => events.map((event) => redactVoiceTraceEvent(event, options));
2156
+ var summarizeVoiceTrace = (events) => {
2157
+ const sorted = filterVoiceTraceEvents(events);
2158
+ const firstEvent = sorted[0];
2159
+ const lastEvent = sorted.at(-1);
2160
+ const lifecycleEvents = sorted.filter((event) => event.type === "call.lifecycle");
2161
+ const startEvent = lifecycleEvents.find((event) => event.payload.type === "start");
2162
+ const endEvent = lifecycleEvents.toReversed().find((event) => event.payload.type === "end");
2163
+ const costEvents = sorted.filter((event) => event.type === "turn.cost");
2164
+ const toolEvents = sorted.filter((event) => event.type === "agent.tool");
2165
+ const startedAt = startEvent?.at ?? firstEvent?.at;
2166
+ const endedAt = endEvent?.at ?? lastEvent?.at;
2167
+ const failed = sorted.some((event) => event.type === "session.error") || endEvent?.payload.disposition === "failed";
2168
+ return {
2169
+ assistantReplyCount: sorted.filter((event) => event.type === "turn.assistant").length,
2170
+ callDurationMs: startedAt !== undefined && endedAt !== undefined ? Math.max(0, endedAt - startedAt) : undefined,
2171
+ cost: {
2172
+ estimatedRelativeCostUnits: costEvents.reduce((total, event) => total + toNumber(event.payload.estimatedRelativeCostUnits), 0),
2173
+ totalBillableAudioMs: costEvents.reduce((total, event) => total + toNumber(event.payload.totalBillableAudioMs), 0)
2174
+ },
2175
+ endedAt,
2176
+ errorCount: sorted.filter((event) => event.type === "session.error").length,
2177
+ eventCount: sorted.length,
2178
+ failed,
2179
+ handoffCount: sorted.filter((event) => event.type === "agent.handoff").length,
2180
+ modelCallCount: sorted.filter((event) => event.type === "agent.model").length,
2181
+ sessionId: firstEvent?.sessionId,
2182
+ startedAt,
2183
+ toolCallCount: toolEvents.length,
2184
+ toolErrorCount: toolEvents.filter((event) => event.payload.status === "error").length,
2185
+ traceId: firstEvent?.traceId,
2186
+ transcriptCount: sorted.filter((event) => event.type === "turn.transcript").length,
2187
+ turnCount: sorted.filter((event) => event.type === "turn.committed").length
2188
+ };
2189
+ };
2190
+ var renderTraceEventMarkdown = (event, startedAt) => {
2191
+ const offset = startedAt === undefined ? `${event.at}` : `+${Math.max(0, event.at - startedAt)}ms`;
2192
+ const label = `- ${offset} [${event.type}]`;
2193
+ switch (event.type) {
2194
+ case "turn.transcript":
2195
+ return `${label} ${event.payload.isFinal ? "final" : "partial"} "${formatTraceValue(event.payload.text)}"`;
2196
+ case "turn.committed":
2197
+ return `${label} committed "${formatTraceValue(event.payload.text)}"`;
2198
+ case "turn.assistant":
2199
+ return event.payload.text ? `${label} assistant "${formatTraceValue(event.payload.text)}"` : `${label} ${formatTraceValue(event.payload.status)}`;
2200
+ case "agent.tool":
2201
+ return `${label} ${formatTraceValue(event.payload.toolName)} ${formatTraceValue(event.payload.status)}`;
2202
+ case "agent.context":
2203
+ return `${label} ${formatTraceValue(event.payload.fromAgentId)} -> ${formatTraceValue(event.payload.targetAgentId)} ${formatTraceValue(event.payload.status)}`;
2204
+ case "agent.handoff":
2205
+ return `${label} ${formatTraceValue(event.payload.fromAgentId)} -> ${formatTraceValue(event.payload.targetAgentId)}`;
2206
+ case "session.error":
2207
+ return `${label} ${formatTraceValue(event.payload.error)}`;
2208
+ case "call.lifecycle":
2209
+ return `${label} ${formatTraceValue(event.payload.type)} ${formatTraceValue(event.payload.disposition)}`.trim();
2210
+ default:
2211
+ return `${label} ${formatTraceValue(event.payload)}`;
2212
+ }
2213
+ };
2214
+ var buildVoiceTraceReplay = (events, options = {}) => ({
2215
+ evaluation: evaluateVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events, options.evaluation),
2216
+ html: renderVoiceTraceHTML(events, options),
2217
+ markdown: renderVoiceTraceMarkdown(events, options),
2218
+ summary: summarizeVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events)
2219
+ });
2220
+ var renderVoiceTraceHTML = (events, options = {}) => {
2221
+ const markdown = renderVoiceTraceMarkdown(events, options);
2222
+ const renderEvents = options.redact ? redactVoiceTraceEvents(events, options.redact) : events;
2223
+ const summary = summarizeVoiceTrace(renderEvents);
2224
+ const evaluation = evaluateVoiceTrace(renderEvents, options.evaluation);
2225
+ const eventRows = filterVoiceTraceEvents(renderEvents).map((event) => {
2226
+ const offset = summary.startedAt === undefined ? event.at : Math.max(0, event.at - summary.startedAt);
2227
+ return [
2228
+ "<tr>",
2229
+ `<td>${escapeHtml(String(offset))}</td>`,
2230
+ `<td>${escapeHtml(event.type)}</td>`,
2231
+ `<td>${escapeHtml(event.turnId ?? "")}</td>`,
2232
+ `<td><code>${escapeHtml(JSON.stringify(event.payload))}</code></td>`,
2233
+ "</tr>"
2234
+ ].join("");
2235
+ }).join(`
2236
+ `);
2237
+ return [
2238
+ "<!doctype html>",
2239
+ '<html lang="en">',
2240
+ "<head>",
2241
+ '<meta charset="utf-8" />',
2242
+ '<meta name="viewport" content="width=device-width, initial-scale=1" />',
2243
+ `<title>${escapeHtml(options.title ?? "Voice Trace")}</title>`,
2244
+ "<style>",
2245
+ "body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;line-height:1.45;background:#f8f7f2;color:#181713}",
2246
+ "main{max-width:1100px;margin:auto}",
2247
+ ".summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:.75rem;margin:1rem 0}",
2248
+ ".card{background:white;border:1px solid #ded9cc;border-radius:12px;padding:1rem}",
2249
+ ".pass{color:#126b3a}.fail{color:#9d2222}",
2250
+ "table{border-collapse:collapse;width:100%;background:white;border:1px solid #ded9cc}",
2251
+ "th,td{border-bottom:1px solid #eee8dc;padding:.65rem;text-align:left;vertical-align:top}",
2252
+ "code{white-space:pre-wrap;word-break:break-word}",
2253
+ "pre{background:#181713;color:#f8f7f2;padding:1rem;border-radius:12px;overflow:auto}",
2254
+ "</style>",
2255
+ "</head>",
2256
+ "<body><main>",
2257
+ `<h1>${escapeHtml(options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim())}</h1>`,
2258
+ `<p class="${evaluation.pass ? "pass" : "fail"}">QA: ${evaluation.pass ? "pass" : "fail"}</p>`,
2259
+ '<section class="summary">',
2260
+ `<div class="card"><strong>Events</strong><br>${summary.eventCount}</div>`,
2261
+ `<div class="card"><strong>Turns</strong><br>${summary.turnCount}</div>`,
2262
+ `<div class="card"><strong>Transcripts</strong><br>${summary.transcriptCount}</div>`,
2263
+ `<div class="card"><strong>Tool errors</strong><br>${summary.toolErrorCount}</div>`,
2264
+ `<div class="card"><strong>Cost units</strong><br>${summary.cost.estimatedRelativeCostUnits}</div>`,
2265
+ "</section>",
2266
+ "<h2>Timeline</h2>",
2267
+ "<table><thead><tr><th>Offset ms</th><th>Type</th><th>Turn</th><th>Payload</th></tr></thead><tbody>",
2268
+ eventRows,
2269
+ "</tbody></table>",
2270
+ "<h2>Markdown Export</h2>",
2271
+ `<pre>${escapeHtml(markdown)}</pre>`,
2272
+ "</main></body></html>"
2273
+ ].join(`
2274
+ `);
2275
+ };
2276
+ var renderVoiceTraceMarkdown = (events, options = {}) => {
2277
+ const sorted = filterVoiceTraceEvents(options.redact ? redactVoiceTraceEvents(events, options.redact) : events);
2278
+ const summary = summarizeVoiceTrace(sorted);
2279
+ const evaluation = evaluateVoiceTrace(sorted, options.evaluation);
2280
+ const lines = [
2281
+ `# ${options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim()}`,
2282
+ "",
2283
+ `Pass: ${evaluation.pass ? "yes" : "no"}`,
2284
+ `Session: ${summary.sessionId ?? "unknown"}`,
2285
+ `Events: ${summary.eventCount}`,
2286
+ `Turns: ${summary.turnCount}`,
2287
+ `Transcripts: ${summary.transcriptCount}`,
2288
+ `Assistant replies: ${summary.assistantReplyCount}`,
2289
+ `Model calls: ${summary.modelCallCount}`,
2290
+ `Tool calls: ${summary.toolCallCount}`,
2291
+ `Handoffs: ${summary.handoffCount}`,
2292
+ `Errors: ${summary.errorCount}`,
2293
+ `Estimated cost units: ${summary.cost.estimatedRelativeCostUnits}`,
2294
+ ""
2295
+ ];
2296
+ if (evaluation.issues.length > 0) {
2297
+ lines.push("## Issues", "");
2298
+ for (const issue of evaluation.issues) {
2299
+ lines.push(`- [${issue.severity}] ${issue.code}: ${issue.message}`);
2300
+ }
2301
+ lines.push("");
2302
+ }
2303
+ lines.push("## Timeline", "");
2304
+ for (const event of sorted) {
2305
+ lines.push(renderTraceEventMarkdown(event, summary.startedAt));
2306
+ }
2307
+ return lines.join(`
2308
+ `);
2309
+ };
2310
+
2311
+ // src/testing/review.ts
2312
+ var roundMetric = (value) => typeof value === "number" ? Math.round(value * 100) / 100 : undefined;
2313
+ var formatMetric = (label, value, unit = "ms") => typeof value === "number" ? `${label}: ${roundMetric(value)}${unit}` : undefined;
2314
+ var findTimelineEvent = (timeline, event, source) => timeline.find((entry) => entry.event === event && (source === undefined || entry.source === source));
2315
+ var formatTimelineText = (entry) => {
2316
+ const parts = [`- ${entry.atMs}ms`, `[${entry.source}]`, entry.event];
2317
+ if (entry.text) {
2318
+ parts.push(`"${entry.text}"`);
2319
+ }
2320
+ if (entry.reason) {
2321
+ parts.push(`reason=${entry.reason}`);
2322
+ }
2323
+ if (typeof entry.bytes === "number") {
2324
+ parts.push(`bytes=${entry.bytes}`);
2325
+ }
2326
+ if (typeof entry.confidence === "number") {
2327
+ parts.push(`confidence=${roundMetric(entry.confidence)}`);
2328
+ }
2329
+ if (entry.name) {
2330
+ parts.push(`name=${entry.name}`);
2331
+ }
2332
+ return parts.join(" ");
2333
+ };
2334
+ var isLowSignalTimelineEvent = (entry) => entry.event === "inbound-media" || entry.event === "inbound-silence-pad" || entry.event === "stt-send" || entry.event === "tts-audio";
2335
+ var summarizeTimelineTraffic = (timeline) => {
2336
+ const summaries = new Map;
2337
+ for (const entry of timeline) {
2338
+ const label = entry.event === "inbound-media" ? "inbound media chunks" : entry.event === "inbound-silence-pad" ? "inbound silence padding" : entry.event === "stt-send" ? "STT audio sends" : entry.event === "tts-audio" ? "post-first TTS audio chunks" : undefined;
2339
+ if (!label) {
2340
+ continue;
2341
+ }
2342
+ const summary = summaries.get(label) ?? {
2343
+ audioMs: 0,
2344
+ bytes: 0,
2345
+ count: 0,
2346
+ label
2347
+ };
2348
+ summary.count += 1;
2349
+ summary.bytes += typeof entry.bytes === "number" ? entry.bytes : 0;
2350
+ summary.audioMs = (summary.audioMs ?? 0) + (typeof entry.chunkDurationMs === "number" ? entry.chunkDurationMs : 0);
2351
+ summaries.set(label, summary);
2352
+ }
2353
+ return [...summaries.values()];
2354
+ };
2355
+ var compactTimeline = (timeline) => {
2356
+ const rows = [];
2357
+ let index = 0;
2358
+ while (index < timeline.length) {
2359
+ const current = timeline[index];
2360
+ if (!current) {
2361
+ break;
2362
+ }
2363
+ const isBurstEvent = isLowSignalTimelineEvent(current) || current.event === "media" && current.source === "twilio";
2364
+ if (!isBurstEvent) {
2365
+ rows.push(formatTimelineText(current));
2366
+ index += 1;
2367
+ continue;
2368
+ }
2369
+ let endIndex = index;
2370
+ let totalBytes = typeof current.bytes === "number" ? current.bytes : 0;
2371
+ let totalChunkDurationMs = typeof current.chunkDurationMs === "number" ? current.chunkDurationMs : 0;
2372
+ while (endIndex + 1 < timeline.length) {
2373
+ const next = timeline[endIndex + 1];
2374
+ if (!next) {
2375
+ break;
2376
+ }
2377
+ if (next.event !== current.event || next.source !== current.source) {
2378
+ break;
2379
+ }
2380
+ totalBytes += typeof next.bytes === "number" ? next.bytes : 0;
2381
+ totalChunkDurationMs += typeof next.chunkDurationMs === "number" ? next.chunkDurationMs : 0;
2382
+ endIndex += 1;
2383
+ }
2384
+ const startAt = current.atMs;
2385
+ const endAt = timeline[endIndex]?.atMs ?? current.atMs;
2386
+ const count = endIndex - index + 1;
2387
+ const parts = [
2388
+ `- ${startAt}-${endAt}ms`,
2389
+ `[${current.source}]`,
2390
+ `${current.event} x${count}`
2391
+ ];
2392
+ if (totalBytes > 0) {
2393
+ parts.push(`bytes=${totalBytes}`);
2394
+ }
2395
+ if (totalChunkDurationMs > 0) {
2396
+ parts.push(`audio=${roundMetric(totalChunkDurationMs)}ms`);
2397
+ }
2398
+ rows.push(parts.join(" "));
2399
+ index = endIndex + 1;
2400
+ }
2401
+ return rows;
2402
+ };
2403
+ var createVoiceCallReviewFromLiveTelephonyReport = (report, options = {}) => {
2404
+ const fixture = report.fixtures?.[0];
2405
+ if (!fixture) {
2406
+ throw new Error("Live telephony review requires at least one fixture result.");
2407
+ }
2408
+ const timeline = [...report.trace ?? []].sort((left, right) => left.atMs - right.atMs);
2409
+ const firstPartial = findTimelineEvent(timeline, "partial", "stt");
2410
+ const commitEvent = findTimelineEvent(timeline, "commit", "turn");
2411
+ const firstTtsAudio = findTimelineEvent(timeline, "tts-first-audio", "benchmark");
2412
+ const firstOutboundMedia = findTimelineEvent(timeline, "media", "twilio");
2413
+ const bargeInEvent = findTimelineEvent(timeline, "barge-in", "benchmark");
2414
+ const clearEvent = findTimelineEvent(timeline, "clear", "twilio");
2415
+ const lastSttText = [...timeline].reverse().find((entry) => entry.source === "stt" && (entry.event === "partial" || entry.event === "final") && typeof entry.text === "string" && entry.text.length > 0)?.text ?? undefined;
2416
+ const latencyBreakdown = [
2417
+ typeof firstPartial?.atMs === "number" ? {
2418
+ label: "start to first partial",
2419
+ valueMs: firstPartial.atMs
2420
+ } : undefined,
2421
+ typeof firstPartial?.atMs === "number" && typeof commitEvent?.atMs === "number" ? {
2422
+ label: "first partial to commit",
2423
+ valueMs: commitEvent.atMs - firstPartial.atMs
2424
+ } : undefined,
2425
+ typeof commitEvent?.atMs === "number" && typeof firstTtsAudio?.atMs === "number" ? {
2426
+ label: "commit to first TTS audio",
2427
+ valueMs: firstTtsAudio.atMs - commitEvent.atMs
2428
+ } : undefined,
2429
+ typeof commitEvent?.atMs === "number" && typeof firstOutboundMedia?.atMs === "number" ? {
2430
+ label: "commit to first outbound media",
2431
+ valueMs: firstOutboundMedia.atMs - commitEvent.atMs
2432
+ } : undefined,
2433
+ typeof bargeInEvent?.atMs === "number" && typeof clearEvent?.atMs === "number" ? {
2434
+ label: "barge-in to clear",
2435
+ valueMs: clearEvent.atMs - bargeInEvent.atMs
2436
+ } : undefined
2437
+ ].filter((value) => value !== undefined && value.valueMs >= 0);
2438
+ const notes = [
2439
+ report.variant?.description,
2440
+ firstPartial?.text ? `First partial: "${firstPartial.text}"` : undefined,
2441
+ lastSttText ? `Last STT text: "${lastSttText}"` : undefined
2442
+ ].filter((value) => typeof value === "string" && value.length > 0);
2443
+ return {
2444
+ config: {
2445
+ preset: options.preset,
2446
+ stt: report.variant ? {
2447
+ description: report.variant.description,
2448
+ id: report.variant.id,
2449
+ model: report.variant.model
2450
+ } : undefined,
2451
+ tts: report.ttsConfig,
2452
+ turnDetection: report.turnDetectionConfig
2453
+ },
2454
+ errors: fixture.errors ?? [],
2455
+ expectedText: fixture.expectedText,
2456
+ fixtureId: fixture.fixtureId,
2457
+ generatedAt: report.generatedAt,
2458
+ latencyBreakdown,
2459
+ notes,
2460
+ path: options.path,
2461
+ summary: {
2462
+ clearLatencyMs: roundMetric(fixture.clearLatencyMs),
2463
+ elapsedMs: roundMetric(fixture.elapsedMs),
2464
+ firstOutboundMediaLatencyMs: roundMetric(fixture.firstOutboundMediaLatencyMs),
2465
+ firstTurnLatencyMs: roundMetric(fixture.firstTurnLatencyMs),
2466
+ markLatencyMs: roundMetric(fixture.markLatencyMs),
2467
+ outboundMediaCount: fixture.outboundMediaCount,
2468
+ pass: fixture.passes,
2469
+ termRecall: roundMetric(fixture.termRecall),
2470
+ turnCount: fixture.turnCount,
2471
+ wordErrorRate: roundMetric(fixture.wordErrorRate)
2472
+ },
2473
+ title: fixture.title ?? "Voice Call Review",
2474
+ timeline,
2475
+ transcript: {
2476
+ actual: fixture.actualText,
2477
+ expected: fixture.expectedText
2478
+ }
2479
+ };
2480
+ };
2481
+ var withVoiceCallReviewId = (id, artifact) => ({
2482
+ ...artifact,
2483
+ id
2484
+ });
2485
+ var toErrorMessage = (error) => {
2486
+ if (typeof error === "string" && error.trim().length > 0) {
2487
+ return error;
2488
+ }
2489
+ if (error instanceof Error && error.message.trim().length > 0) {
2490
+ return error.message;
2491
+ }
2492
+ return "Unknown call error";
2493
+ };
2494
+ var createVoiceCallReviewRecorder = (options = {}) => {
2495
+ const now = options.now ?? (() => Date.now());
2496
+ const startedAt = now();
2497
+ const errors = [];
2498
+ const timeline = [];
2499
+ const committedTurns = [];
2500
+ const committedTurnIds = new Set;
2501
+ const push = (source, event, fields = {}) => {
2502
+ timeline.push({
2503
+ atMs: Math.max(0, now() - startedAt),
2504
+ event,
2505
+ source,
2506
+ ...fields
2507
+ });
2508
+ };
2509
+ return {
2510
+ finalize: () => {
2511
+ const sortedTimeline = [...timeline].sort((left, right) => left.atMs - right.atMs);
2512
+ const firstPartial = findTimelineEvent(sortedTimeline, "partial", "stt");
2513
+ const commitEvent = findTimelineEvent(sortedTimeline, "commit", "turn");
2514
+ const firstTtsAudio = findTimelineEvent(sortedTimeline, "tts-first-audio", "benchmark");
2515
+ const firstOutboundMedia = findTimelineEvent(sortedTimeline, "media", "twilio");
2516
+ const bargeInEvent = findTimelineEvent(sortedTimeline, "barge-in", "benchmark");
2517
+ const clearEvent = findTimelineEvent(sortedTimeline, "clear", "twilio");
2518
+ const markEvent = findTimelineEvent(sortedTimeline, "mark", "twilio");
2519
+ const elapsedMs = sortedTimeline.at(-1)?.atMs ?? 0;
2520
+ const lastSttText = [...sortedTimeline].reverse().find((entry) => entry.source === "stt" && (entry.event === "partial" || entry.event === "final") && typeof entry.text === "string" && entry.text.length > 0)?.text ?? undefined;
2521
+ const latencyBreakdown = [
2522
+ typeof firstPartial?.atMs === "number" ? {
2523
+ label: "start to first partial",
2524
+ valueMs: firstPartial.atMs
2525
+ } : undefined,
2526
+ typeof firstPartial?.atMs === "number" && typeof commitEvent?.atMs === "number" ? {
2527
+ label: "first partial to commit",
2528
+ valueMs: commitEvent.atMs - firstPartial.atMs
2529
+ } : undefined,
2530
+ typeof commitEvent?.atMs === "number" && typeof firstTtsAudio?.atMs === "number" ? {
2531
+ label: "commit to first TTS audio",
2532
+ valueMs: firstTtsAudio.atMs - commitEvent.atMs
2533
+ } : undefined,
2534
+ typeof commitEvent?.atMs === "number" && typeof firstOutboundMedia?.atMs === "number" ? {
2535
+ label: "commit to first outbound media",
2536
+ valueMs: firstOutboundMedia.atMs - commitEvent.atMs
2537
+ } : undefined,
2538
+ typeof bargeInEvent?.atMs === "number" && typeof clearEvent?.atMs === "number" ? {
2539
+ label: "barge-in to clear",
2540
+ valueMs: clearEvent.atMs - bargeInEvent.atMs
2541
+ } : undefined
2542
+ ].filter((value) => value !== undefined && value.valueMs >= 0);
2543
+ return {
2544
+ config: options.config,
2545
+ errors,
2546
+ fixtureId: options.fixtureId,
2547
+ generatedAt: now(),
2548
+ latencyBreakdown,
2549
+ notes: [
2550
+ firstPartial?.text ? `First partial: "${firstPartial.text}"` : undefined,
2551
+ lastSttText ? `Last STT text: "${lastSttText}"` : undefined
2552
+ ].filter((value) => typeof value === "string"),
2553
+ path: options.path,
2554
+ summary: {
2555
+ clearLatencyMs: roundMetric(typeof clearEvent?.atMs === "number" && typeof bargeInEvent?.atMs === "number" ? clearEvent.atMs - bargeInEvent.atMs : undefined),
2556
+ elapsedMs: roundMetric(elapsedMs),
2557
+ firstOutboundMediaLatencyMs: roundMetric(firstOutboundMedia?.atMs),
2558
+ firstTurnLatencyMs: roundMetric(commitEvent?.atMs),
2559
+ markLatencyMs: roundMetric(markEvent?.atMs),
2560
+ outboundMediaCount: sortedTimeline.filter((entry) => entry.source === "twilio" && entry.event === "media").length,
2561
+ pass: errors.length === 0,
2562
+ turnCount: committedTurns.length
2563
+ },
2564
+ title: options.title ?? "Voice Call Review",
2565
+ timeline: sortedTimeline,
2566
+ transcript: {
2567
+ actual: committedTurns.join(" ").trim()
2568
+ }
2569
+ };
2570
+ },
2571
+ recordError: (error) => {
2572
+ const message = toErrorMessage(error);
2573
+ errors.push(message);
2574
+ push("turn", "error", {
2575
+ reason: message
2576
+ });
2577
+ },
2578
+ recordTwilioInbound: (input) => {
2579
+ push("twilio", input.event, {
2580
+ bytes: input.bytes,
2581
+ chunkDurationMs: input.chunkDurationMs,
2582
+ name: input.name,
2583
+ reason: input.reason,
2584
+ text: input.text,
2585
+ track: input.track
2586
+ });
2587
+ },
2588
+ recordTwilioOutbound: (input) => {
2589
+ push("twilio", input.event, {
2590
+ bytes: input.bytes,
2591
+ chunkDurationMs: input.chunkDurationMs,
2592
+ name: input.name,
2593
+ reason: input.reason,
2594
+ text: input.text,
2595
+ track: input.track
2596
+ });
2597
+ },
2598
+ recordVoiceMessage: (message) => {
2599
+ switch (message.type) {
2600
+ case "partial":
2601
+ case "final":
2602
+ push("stt", message.type, {
2603
+ confidence: message.transcript.confidence,
2604
+ text: message.transcript.text
2605
+ });
2606
+ return;
2607
+ case "assistant":
2608
+ push("turn", "assistant", {
2609
+ text: message.text
2610
+ });
2611
+ return;
2612
+ case "audio":
2613
+ push("benchmark", timeline.some((entry) => entry.event === "tts-first-audio") ? "tts-audio" : "tts-first-audio", {
2614
+ bytes: Math.floor(message.chunkBase64.length * 3 / 4)
2615
+ });
2616
+ return;
2617
+ case "turn":
2618
+ if (committedTurnIds.has(message.turn.id)) {
2619
+ return;
2620
+ }
2621
+ committedTurnIds.add(message.turn.id);
2622
+ committedTurns.push(message.turn.text);
2623
+ push("turn", "commit", {
2624
+ confidence: message.turn.quality?.averageConfidence,
2625
+ text: message.turn.text
2626
+ });
2627
+ return;
2628
+ case "error":
2629
+ errors.push(message.message);
2630
+ push("turn", "error", {
2631
+ reason: message.message
2632
+ });
2633
+ return;
2634
+ case "complete":
2635
+ push("turn", "complete", {
2636
+ text: message.sessionId
2637
+ });
2638
+ return;
2639
+ case "session":
2640
+ push("turn", "session", {
2641
+ reason: message.status,
2642
+ text: message.sessionId
2643
+ });
2644
+ return;
2645
+ case "pong":
2646
+ push("benchmark", "pong");
2647
+ }
2648
+ }
2649
+ };
2650
+ };
2651
+ var renderConfigSection = (config) => {
2652
+ if (!config) {
2653
+ return "";
2654
+ }
2655
+ return [
2656
+ "## Config",
2657
+ "",
2658
+ "```json",
2659
+ JSON.stringify(config, null, 2),
2660
+ "```"
2661
+ ].join(`
2662
+ `);
2663
+ };
2664
+ var renderTimeline = (timeline) => {
2665
+ const focusedTimeline = timeline.filter((entry) => !isLowSignalTimelineEvent(entry));
2666
+ if (focusedTimeline.length === 0) {
2667
+ return `## Timeline
2668
+
2669
+ _No timeline events captured._`;
2670
+ }
2671
+ const lines = compactTimeline(focusedTimeline);
2672
+ return ["## Timeline", "", ...lines].join(`
2673
+ `);
2674
+ };
2675
+ var renderTransportSummary = (timeline) => {
2676
+ const summaries = summarizeTimelineTraffic(timeline);
2677
+ if (summaries.length === 0) {
2678
+ return "";
2679
+ }
2680
+ return [
2681
+ "## Transport Summary",
2682
+ "",
2683
+ ...summaries.map((summary) => {
2684
+ const parts = [`- ${summary.label}: ${summary.count}`];
2685
+ if (summary.bytes > 0) {
2686
+ parts.push(`${summary.bytes} bytes`);
2687
+ }
2688
+ if ((summary.audioMs ?? 0) > 0) {
2689
+ parts.push(`${roundMetric(summary.audioMs)}ms audio`);
2690
+ }
2691
+ return parts.join(", ");
2692
+ })
2693
+ ].join(`
2694
+ `);
2695
+ };
2696
+ var renderLatencyBreakdown = (breakdown) => {
2697
+ if (breakdown.length === 0) {
2698
+ return "";
2699
+ }
2700
+ return [
2701
+ "## Latency Breakdown",
2702
+ "",
2703
+ ...breakdown.map((entry) => `- ${entry.label}: ${roundMetric(entry.valueMs)}ms`)
2704
+ ].join(`
2705
+ `);
2706
+ };
2707
+ var renderVoiceCallReviewHTML = (artifact) => {
2708
+ const notes = artifact.notes.map((note) => `<li>${escapeHtml(note)}</li>`).join("");
2709
+ const latency = artifact.latencyBreakdown.map((entry) => `<li><strong>${escapeHtml(entry.label)}:</strong> ${roundMetric(entry.valueMs)}ms</li>`).join("");
2710
+ const transport = summarizeTimelineTraffic(artifact.timeline).map((summary) => {
2711
+ const parts = [`${summary.count}`, "events"];
2712
+ if (summary.bytes > 0) {
2713
+ parts.push(`${summary.bytes} bytes`);
2714
+ }
2715
+ if ((summary.audioMs ?? 0) > 0) {
2716
+ parts.push(`${roundMetric(summary.audioMs)}ms audio`);
2717
+ }
2718
+ return `<li><strong>${escapeHtml(summary.label)}:</strong> ${escapeHtml(parts.join(", "))}</li>`;
2719
+ }).join("");
2720
+ const timeline = compactTimeline(artifact.timeline.filter((entry) => !isLowSignalTimelineEvent(entry))).map((line) => `<li>${escapeHtml(line.replace(/^- /u, ""))}</li>`).join("");
2721
+ return `<!doctype html>
2722
+ <html lang="en">
2723
+ <head>
2724
+ <meta charset="utf-8" />
2725
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
2726
+ <title>${escapeHtml(artifact.title)}</title>
2727
+ <style>
2728
+ :root { color-scheme: dark; }
2729
+ body { font-family: ui-sans-serif, system-ui, sans-serif; margin: 0; padding: 24px; background: #0b0d10; color: #f4f4f5; }
2730
+ main { max-width: 980px; margin: 0 auto; display: grid; gap: 16px; }
2731
+ section { background: #13161b; border: 1px solid #232833; border-radius: 16px; padding: 18px; }
2732
+ h1, h2 { margin: 0 0 12px; }
2733
+ ul { margin: 0; padding-left: 20px; display: grid; gap: 8px; }
2734
+ code, pre { font-family: ui-monospace, SFMono-Regular, monospace; }
2735
+ pre { white-space: pre-wrap; overflow-wrap: anywhere; background: #0f1217; border-radius: 12px; padding: 14px; border: 1px solid #232833; }
2736
+ .grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
2737
+ .metric { display: grid; gap: 4px; }
2738
+ .label { color: #a1a1aa; font-size: 0.82rem; text-transform: uppercase; letter-spacing: 0.08em; }
2739
+ .value { font-size: 1.05rem; }
2740
+ </style>
2741
+ </head>
2742
+ <body>
2743
+ <main>
2744
+ <section>
2745
+ <h1>${escapeHtml(artifact.title)}</h1>
2746
+ <div class="grid">
2747
+ <div class="metric"><div class="label">Pass</div><div class="value">${artifact.summary.pass ? "yes" : "no"}</div></div>
2748
+ <div class="metric"><div class="label">First Turn</div><div class="value">${artifact.summary.firstTurnLatencyMs ?? "n/a"}ms</div></div>
2749
+ <div class="metric"><div class="label">First Outbound Media</div><div class="value">${artifact.summary.firstOutboundMediaLatencyMs ?? "n/a"}ms</div></div>
2750
+ <div class="metric"><div class="label">Turn Count</div><div class="value">${artifact.summary.turnCount ?? "n/a"}</div></div>
2751
+ </div>
2752
+ </section>
2753
+ <section>
2754
+ <h2>Transcript</h2>
2755
+ <ul>
2756
+ <li><strong>Expected:</strong> ${escapeHtml(artifact.transcript.expected ?? "n/a")}</li>
2757
+ <li><strong>Actual:</strong> ${escapeHtml(artifact.transcript.actual || "n/a")}</li>
2758
+ </ul>
2759
+ </section>
2760
+ <section>
2761
+ <h2>Notes</h2>
2762
+ <ul>${notes || "<li>No notes.</li>"}</ul>
2763
+ </section>
2764
+ <section>
2765
+ <h2>Latency Breakdown</h2>
2766
+ <ul>${latency || "<li>No latency data.</li>"}</ul>
2767
+ </section>
2768
+ <section>
2769
+ <h2>Transport Summary</h2>
2770
+ <ul>${transport || "<li>No transport data.</li>"}</ul>
2771
+ </section>
2772
+ <section>
2773
+ <h2>Timeline</h2>
2774
+ <ul>${timeline || "<li>No timeline events.</li>"}</ul>
2775
+ </section>
2776
+ <section>
2777
+ <h2>Config</h2>
2778
+ <pre>${escapeHtml(JSON.stringify(artifact.config ?? {}, null, 2))}</pre>
2779
+ </section>
2780
+ </main>
2781
+ </body>
2782
+ </html>`;
2783
+ };
2784
+ var renderVoiceCallReviewMarkdown = (artifact) => {
2785
+ const summaryLines = [
2786
+ `- pass: ${artifact.summary.pass ? "yes" : "no"}`,
2787
+ formatMetric("first turn", artifact.summary.firstTurnLatencyMs),
2788
+ formatMetric("first outbound media", artifact.summary.firstOutboundMediaLatencyMs),
2789
+ formatMetric("mark", artifact.summary.markLatencyMs),
2790
+ formatMetric("clear", artifact.summary.clearLatencyMs),
2791
+ formatMetric("elapsed", artifact.summary.elapsedMs),
2792
+ typeof artifact.summary.wordErrorRate === "number" ? `- word error rate: ${artifact.summary.wordErrorRate}` : undefined,
2793
+ typeof artifact.summary.termRecall === "number" ? `- term recall: ${artifact.summary.termRecall}` : undefined,
2794
+ typeof artifact.summary.turnCount === "number" ? `- turn count: ${artifact.summary.turnCount}` : undefined,
2795
+ typeof artifact.summary.outboundMediaCount === "number" ? `- outbound media count: ${artifact.summary.outboundMediaCount}` : undefined
2796
+ ].filter((value) => typeof value === "string");
2797
+ const notes = artifact.notes.length ? ["## Notes", "", ...artifact.notes.map((note) => `- ${note}`)].join(`
2798
+ `) : "";
2799
+ const errors = artifact.errors.length ? ["## Errors", "", ...artifact.errors.map((error) => `- ${error}`)].join(`
2800
+ `) : "";
2801
+ const latency = renderLatencyBreakdown(artifact.latencyBreakdown);
2802
+ const transportSummary = renderTransportSummary(artifact.timeline);
2803
+ return [
2804
+ `# ${artifact.title}`,
2805
+ "",
2806
+ artifact.path ? `Source: \`${artifact.path}\`` : undefined,
2807
+ artifact.fixtureId ? `Fixture: \`${artifact.fixtureId}\`` : undefined,
2808
+ "",
2809
+ "## Summary",
2810
+ "",
2811
+ ...summaryLines,
2812
+ "",
2813
+ "## Transcript",
2814
+ "",
2815
+ `- expected: ${artifact.transcript.expected ?? "_n/a_"}`,
2816
+ `- actual: ${artifact.transcript.actual}`,
2817
+ "",
2818
+ notes,
2819
+ notes ? "" : undefined,
2820
+ latency,
2821
+ latency ? "" : undefined,
2822
+ transportSummary,
2823
+ transportSummary ? "" : undefined,
2824
+ errors,
2825
+ errors ? "" : undefined,
2826
+ renderConfigSection(artifact.config),
2827
+ renderConfigSection(artifact.config) ? "" : undefined,
2828
+ renderTimeline(artifact.timeline)
2829
+ ].filter((value) => typeof value === "string").join(`
2830
+ `);
2831
+ };
2832
+
2833
+ // src/drizzle/runtimeStorage.ts
2834
+ var voiceAuditDeliveriesTable = voiceDocumentTable("voice_audit_deliveries");
2835
+ var voiceAuditTable = voiceDocumentTable("voice_audit");
2836
+ var voiceCampaignsTable = voiceDocumentTable("voice_campaigns");
2837
+ var voiceEventsTable = voiceDocumentTable("voice_events");
2838
+ var voiceExternalObjectsTable = voiceDocumentTable("voice_external_objects");
2839
+ var voiceReviewsTable = voiceDocumentTable("voice_reviews");
2840
+ var voiceSessionsTable = voiceDocumentTable("voice_sessions");
2841
+ var voiceTasksTable = voiceDocumentTable("voice_tasks");
2842
+ var voiceTelephonyWebhookIdempotencyTable = voiceDocumentTable("voice_telephony_webhook_idempotency");
2843
+ var voiceTraceDeliveriesTable = voiceDocumentTable("voice_trace_deliveries");
2844
+ var voiceTracesTable = voiceDocumentTable("voice_traces");
2845
+ var voiceRuntimeStorageDrizzleSchema = {
2846
+ voiceAudit: voiceAuditTable,
2847
+ voiceAuditDeliveries: voiceAuditDeliveriesTable,
2848
+ voiceCampaigns: voiceCampaignsTable,
2849
+ voiceEvents: voiceEventsTable,
2850
+ voiceExternalObjects: voiceExternalObjectsTable,
2851
+ voiceReviews: voiceReviewsTable,
2852
+ voiceSessions: voiceSessionsTable,
2853
+ voiceTasks: voiceTasksTable,
2854
+ voiceTelephonyWebhookIdempotency: voiceTelephonyWebhookIdempotencyTable,
2855
+ voiceTraceDeliveries: voiceTraceDeliveriesTable,
2856
+ voiceTraces: voiceTracesTable
2857
+ };
2858
+ var createDrizzleSessionStore = (db) => {
2859
+ const store = createVoiceDrizzleRecordStore({
2860
+ db,
2861
+ decorate: (_id, value) => value,
2862
+ getSortAt: (value) => value.lastActivityAt ?? value.createdAt,
2863
+ table: voiceSessionsTable
2864
+ });
2865
+ const getOrCreate = async (id) => {
2866
+ const existing = await store.get(id);
2867
+ if (existing) {
2868
+ return existing;
2869
+ }
2870
+ const session = createVoiceSessionRecord(id);
2871
+ await store.set(id, session);
2872
+ return session;
2873
+ };
2874
+ return {
2875
+ get: store.get,
2876
+ getOrCreate,
2877
+ remove: store.remove,
2878
+ set: store.set,
2879
+ list: async () => (await store.list()).map((session) => toVoiceSessionSummary(session)).sort((first, second) => (second.lastActivityAt ?? second.createdAt) - (first.lastActivityAt ?? first.createdAt))
2880
+ };
2881
+ };
2882
+ var createDrizzleReviewStore = (db) => createVoiceDrizzleRecordStore({
2883
+ db,
2884
+ decorate: (id, value) => withVoiceCallReviewId(id, value),
2885
+ getSortAt: (value) => value.generatedAt ?? 0,
2886
+ table: voiceReviewsTable
2887
+ });
2888
+ var createDrizzleTaskStore = (db) => createVoiceDrizzleRecordStore({
2889
+ db,
2890
+ decorate: (id, value) => withVoiceOpsTaskId(id, value),
2891
+ getSortAt: (value) => value.createdAt,
2892
+ table: voiceTasksTable
2893
+ });
2894
+ var createDrizzleEventStore = (db) => createVoiceDrizzleRecordStore({
2895
+ db,
2896
+ decorate: (id, value) => withVoiceIntegrationEventId(id, value),
2897
+ getSortAt: (value) => value.createdAt,
2898
+ table: voiceEventsTable
2899
+ });
2900
+ var createDrizzleExternalObjectMapStore = (db) => {
2901
+ const store = createVoiceDrizzleRecordStore({
2902
+ db,
2903
+ decorate: (id, value) => ({
2904
+ ...value,
2905
+ id
2906
+ }),
2907
+ getSortAt: (value) => value.updatedAt,
2908
+ table: voiceExternalObjectsTable
2909
+ });
2910
+ const find = async (input) => (await store.list()).find((mapping) => mapping.provider === input.provider && mapping.sourceId === input.sourceId && (input.sinkId === undefined || mapping.sinkId === input.sinkId) && (input.sourceType === undefined || mapping.sourceType === input.sourceType));
2911
+ return {
2912
+ ...store,
2913
+ find
2914
+ };
2915
+ };
2916
+ var createDrizzleTraceEventStore = (db) => {
2917
+ const store = createVoiceDrizzleRecordStore({
2918
+ db,
2919
+ decorate: (_id, value) => value,
2920
+ getSortAt: (value) => value.at,
2921
+ table: voiceTracesTable
2922
+ });
2923
+ const append = async (event) => {
2924
+ const stored = createVoiceTraceEvent(event);
2925
+ await store.set(stored.id, stored);
2926
+ return stored;
2927
+ };
2928
+ return {
2929
+ append,
2930
+ get: store.get,
2931
+ remove: store.remove,
2932
+ list: async (filter) => filterVoiceTraceEvents(await store.list(), filter)
2933
+ };
2934
+ };
2935
+ var createDrizzleTraceSinkDeliveryStore = (db) => createVoiceDrizzleRecordStore({
2936
+ db,
2937
+ decorate: (id, value) => ({
2938
+ ...value,
2939
+ id
2940
+ }),
2941
+ getSortAt: (value) => value.createdAt,
2942
+ table: voiceTraceDeliveriesTable
2943
+ });
2944
+ var createDrizzleAuditEventStore = (db) => {
2945
+ const store = createVoiceDrizzleRecordStore({
2946
+ db,
2947
+ decorate: (_id, value) => value,
2948
+ getSortAt: (value) => value.at,
2949
+ table: voiceAuditTable
2950
+ });
2951
+ const append = async (event) => {
2952
+ const stored = createVoiceAuditEvent(event);
2953
+ await store.set(stored.id, stored);
2954
+ return stored;
2955
+ };
2956
+ return {
2957
+ append,
2958
+ get: store.get,
2959
+ list: async (filter) => filterVoiceAuditEvents(await store.list(), filter)
2960
+ };
2961
+ };
2962
+ var createDrizzleAuditSinkDeliveryStore = (db) => createVoiceDrizzleRecordStore({
2963
+ db,
2964
+ decorate: (id, value) => ({
2965
+ ...value,
2966
+ id
2967
+ }),
2968
+ getSortAt: (value) => value.createdAt,
2969
+ table: voiceAuditDeliveriesTable
2970
+ });
2971
+ var createDrizzleTelephonyWebhookIdempotencyStore = (db) => createVoiceDrizzleRecordStore({
2972
+ db,
2973
+ decorate: (_id, value) => value,
2974
+ getSortAt: (value) => value.updatedAt,
2975
+ table: voiceTelephonyWebhookIdempotencyTable
2976
+ });
2977
+ var createDrizzleCampaignStore = (db) => createVoiceDrizzleRecordStore({
2978
+ db,
2979
+ decorate: (_id, value) => value,
2980
+ getSortAt: (value) => value.campaign.createdAt,
2981
+ table: voiceCampaignsTable
2982
+ });
2983
+ var createVoiceDrizzleAuditEventStore = (options) => createDrizzleAuditEventStore(options.db);
2984
+ var createVoiceDrizzleAuditSinkDeliveryStore = (options) => createDrizzleAuditSinkDeliveryStore(options.db);
2985
+ var createVoiceDrizzleCampaignStore = (options) => createDrizzleCampaignStore(options.db);
2986
+ var createVoiceDrizzleExternalObjectMapStore = (options) => createDrizzleExternalObjectMapStore(options.db);
2987
+ var createVoiceDrizzleIntegrationEventStore = (options) => createDrizzleEventStore(options.db);
2988
+ var createVoiceDrizzleReviewStore = (options) => createDrizzleReviewStore(options.db);
2989
+ var createVoiceDrizzleRuntimeStorage = (options) => ({
2990
+ audit: createDrizzleAuditEventStore(options.db),
2991
+ auditDeliveries: createDrizzleAuditSinkDeliveryStore(options.db),
2992
+ campaigns: createDrizzleCampaignStore(options.db),
2993
+ events: createDrizzleEventStore(options.db),
2994
+ externalObjects: createDrizzleExternalObjectMapStore(options.db),
2995
+ incidentBundles: createVoiceDrizzleIncidentBundleStore({
2996
+ db: options.db
2997
+ }),
2998
+ memories: createVoiceDrizzleAssistantMemoryStore({ db: options.db }),
2999
+ reviews: createDrizzleReviewStore(options.db),
3000
+ session: createDrizzleSessionStore(options.db),
3001
+ tasks: createDrizzleTaskStore(options.db),
3002
+ traceDeliveries: createDrizzleTraceSinkDeliveryStore(options.db),
3003
+ traces: createDrizzleTraceEventStore(options.db)
3004
+ });
3005
+ var createVoiceDrizzleSessionStore = (options) => createDrizzleSessionStore(options.db);
3006
+ var createVoiceDrizzleTaskStore = (options) => createDrizzleTaskStore(options.db);
3007
+ var createVoiceDrizzleTelephonyWebhookIdempotencyStore = (options) => createDrizzleTelephonyWebhookIdempotencyStore(options.db);
3008
+ var createVoiceDrizzleTraceEventStore = (options) => createDrizzleTraceEventStore(options.db);
3009
+ var createVoiceDrizzleTraceSinkDeliveryStore = (options) => createDrizzleTraceSinkDeliveryStore(options.db);
3010
+
3011
+ // src/drizzle/index.ts
3012
+ var voiceDrizzleSchema = {
3013
+ ...voiceRuntimeStorageDrizzleSchema,
3014
+ voiceAssistantMemory: voiceAssistantMemoryTable,
3015
+ voiceEvalBaseline: voiceEvalBaselineTable,
3016
+ voiceHandoffDeliveries: voiceHandoffDeliveriesTable,
3017
+ voiceIncidentBundles: voiceIncidentBundlesTable,
3018
+ voiceObservabilityExportReceipts: voiceObservabilityExportDeliveryReceiptsTable,
3019
+ voiceRealCallProfileEvidence: voiceRealCallProfileEvidenceTable,
3020
+ voiceRealCallProfileRecoveryJobs: voiceRealCallProfileRecoveryJobsTable
3021
+ };
3022
+ export {
3023
+ voiceTracesTable,
3024
+ voiceTraceDeliveriesTable,
3025
+ voiceTelephonyWebhookIdempotencyTable,
3026
+ voiceTasksTable,
3027
+ voiceSessionsTable,
3028
+ voiceRuntimeStorageDrizzleSchema,
3029
+ voiceReviewsTable,
3030
+ voiceRealCallProfileRecoveryJobsTable,
3031
+ voiceRealCallProfileEvidenceTable,
3032
+ voiceObservabilityExportDeliveryReceiptsTable,
3033
+ voiceIncidentBundlesTable,
3034
+ voiceHandoffDeliveriesTable,
3035
+ voiceExternalObjectsTable,
3036
+ voiceEventsTable,
3037
+ voiceEvalBaselineTable,
3038
+ voiceDrizzleSchema,
3039
+ voiceDocumentTable,
3040
+ voiceCampaignsTable,
3041
+ voiceAuditTable,
3042
+ voiceAuditDeliveriesTable,
3043
+ voiceAssistantMemoryTable,
3044
+ createVoiceDrizzleTraceSinkDeliveryStore,
3045
+ createVoiceDrizzleTraceEventStore,
3046
+ createVoiceDrizzleTelephonyWebhookIdempotencyStore,
3047
+ createVoiceDrizzleTaskStore,
3048
+ createVoiceDrizzleSessionStore,
3049
+ createVoiceDrizzleRuntimeStorage,
3050
+ createVoiceDrizzleReviewStore,
3051
+ createVoiceDrizzleRecordStore,
3052
+ createVoiceDrizzleRealCallProfileRecoveryJobStore,
3053
+ createVoiceDrizzleRealCallProfileEvidenceStore,
3054
+ createVoiceDrizzleObservabilityExportDeliveryReceiptStore,
3055
+ createVoiceDrizzleIntegrationEventStore,
3056
+ createVoiceDrizzleIncidentBundleStore,
3057
+ createVoiceDrizzleHandoffDeliveryStore,
3058
+ createVoiceDrizzleExternalObjectMapStore,
3059
+ createVoiceDrizzleEvalBaselineStore,
3060
+ createVoiceDrizzleCampaignStore,
3061
+ createVoiceDrizzleAuditSinkDeliveryStore,
3062
+ createVoiceDrizzleAuditEventStore,
3063
+ createVoiceDrizzleAssistantMemoryStore
3064
+ };