@absolutejs/voice 0.0.22-beta.192 → 0.0.22-beta.193

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.
package/dist/index.js CHANGED
@@ -5652,6 +5652,192 @@ var maybeCompleteCampaign = (record) => {
5652
5652
  record.campaign.status = "completed";
5653
5653
  }
5654
5654
  };
5655
+ var normalizeHour = (hour) => {
5656
+ if (!Number.isFinite(hour)) {
5657
+ return 0;
5658
+ }
5659
+ return Math.min(24, Math.max(0, hour));
5660
+ };
5661
+ var getCampaignWindowMinute = (at, offsetMinutes = 0) => {
5662
+ const date = new Date(at + offsetMinutes * 60000);
5663
+ return {
5664
+ dayOfWeek: date.getUTCDay(),
5665
+ minuteOfDay: date.getUTCHours() * 60 + date.getUTCMinutes()
5666
+ };
5667
+ };
5668
+ var isWithinCampaignTimeWindow = (window, at) => {
5669
+ const { dayOfWeek, minuteOfDay } = getCampaignWindowMinute(at, window.timeZoneOffsetMinutes ?? 0);
5670
+ if (window.daysOfWeek && !window.daysOfWeek.includes(dayOfWeek)) {
5671
+ return false;
5672
+ }
5673
+ const start = normalizeHour(window.startHour) * 60;
5674
+ const end = normalizeHour(window.endHour) * 60;
5675
+ if (start === end) {
5676
+ return true;
5677
+ }
5678
+ if (start < end) {
5679
+ return minuteOfDay >= start && minuteOfDay < end;
5680
+ }
5681
+ return minuteOfDay >= start || minuteOfDay < end;
5682
+ };
5683
+ var getCampaignRetryBackoffMs = (policy, attemptNumber) => {
5684
+ const backoff = policy?.backoffMs;
5685
+ if (Array.isArray(backoff)) {
5686
+ return Math.max(0, backoff[Math.min(backoff.length - 1, attemptNumber - 1)] ?? 0);
5687
+ }
5688
+ const value = Math.max(0, backoff ?? 0);
5689
+ if (policy?.maxBackoffMs === undefined) {
5690
+ return value;
5691
+ }
5692
+ return Math.min(value, Math.max(0, policy.maxBackoffMs));
5693
+ };
5694
+ var getLastCampaignAttempt = (record, recipientId) => record.attempts.filter((attempt) => attempt.recipientId === recipientId).sort((left, right) => right.createdAt - left.createdAt)[0];
5695
+ var parseCsvLine = (line) => {
5696
+ const values = [];
5697
+ let current = "";
5698
+ let quoted = false;
5699
+ for (let index = 0;index < line.length; index += 1) {
5700
+ const character = line[index];
5701
+ const next = line[index + 1];
5702
+ if (character === '"' && quoted && next === '"') {
5703
+ current += '"';
5704
+ index += 1;
5705
+ continue;
5706
+ }
5707
+ if (character === '"') {
5708
+ quoted = !quoted;
5709
+ continue;
5710
+ }
5711
+ if (character === "," && !quoted) {
5712
+ values.push(current.trim());
5713
+ current = "";
5714
+ continue;
5715
+ }
5716
+ current += character;
5717
+ }
5718
+ values.push(current.trim());
5719
+ return values;
5720
+ };
5721
+ var parseVoiceCampaignCSVRows = (csv) => {
5722
+ const lines = csv.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
5723
+ if (lines.length === 0) {
5724
+ return [];
5725
+ }
5726
+ const headers = parseCsvLine(lines[0] ?? "");
5727
+ return lines.slice(1).map((line) => {
5728
+ const values = parseCsvLine(line);
5729
+ const row = {};
5730
+ headers.forEach((header, index) => {
5731
+ row[header] = values[index] ?? "";
5732
+ });
5733
+ return row;
5734
+ });
5735
+ };
5736
+ var normalizeCampaignPhone = (value) => {
5737
+ if (typeof value !== "string" && typeof value !== "number") {
5738
+ return;
5739
+ }
5740
+ const raw = String(value).trim();
5741
+ if (!raw) {
5742
+ return;
5743
+ }
5744
+ const hasPlus = raw.startsWith("+");
5745
+ const digits = raw.replace(/\D/g, "");
5746
+ if (digits.length < 8 || digits.length > 15) {
5747
+ return;
5748
+ }
5749
+ return hasPlus ? `+${digits}` : `+${digits}`;
5750
+ };
5751
+ var toCampaignScalar = (value) => {
5752
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
5753
+ return value;
5754
+ }
5755
+ return;
5756
+ };
5757
+ var truthyConsent = (value) => {
5758
+ if (value === true) {
5759
+ return true;
5760
+ }
5761
+ if (typeof value === "number") {
5762
+ return value > 0;
5763
+ }
5764
+ if (typeof value !== "string") {
5765
+ return false;
5766
+ }
5767
+ return ["1", "true", "yes", "y", "consented", "opt-in", "opted-in"].includes(value.trim().toLowerCase());
5768
+ };
5769
+ var importVoiceCampaignRecipients = (options) => {
5770
+ const rows = options.rows ?? (options.csv ? parseVoiceCampaignCSVRows(options.csv) : []);
5771
+ const phoneColumn = options.phoneColumn ?? "phone";
5772
+ const nameColumn = options.nameColumn ?? "name";
5773
+ const idColumn = options.idColumn ?? "id";
5774
+ const consentColumn = options.consentColumn ?? "consent";
5775
+ const dedupe = options.dedupe ?? true;
5776
+ const seenPhones = new Set;
5777
+ const accepted = [];
5778
+ const rejected = [];
5779
+ let duplicates = 0;
5780
+ rows.forEach((row, index) => {
5781
+ const rowNumber = index + 1;
5782
+ const phoneValue = row[phoneColumn];
5783
+ if (phoneValue === undefined || phoneValue === null || phoneValue === "") {
5784
+ rejected.push({
5785
+ code: "missing-phone",
5786
+ message: `Campaign recipient row ${rowNumber} is missing a phone number.`,
5787
+ row: rowNumber,
5788
+ value: phoneValue
5789
+ });
5790
+ return;
5791
+ }
5792
+ const phone = normalizeCampaignPhone(phoneValue);
5793
+ if (!phone) {
5794
+ rejected.push({
5795
+ code: "invalid-phone",
5796
+ message: `Campaign recipient row ${rowNumber} has an invalid phone number.`,
5797
+ row: rowNumber,
5798
+ value: phoneValue
5799
+ });
5800
+ return;
5801
+ }
5802
+ if (options.requireConsent && !truthyConsent(row[consentColumn])) {
5803
+ rejected.push({
5804
+ code: "missing-consent",
5805
+ message: `Campaign recipient row ${rowNumber} is missing required consent.`,
5806
+ row: rowNumber,
5807
+ value: row[consentColumn]
5808
+ });
5809
+ return;
5810
+ }
5811
+ if (dedupe && seenPhones.has(phone)) {
5812
+ duplicates += 1;
5813
+ rejected.push({
5814
+ code: "duplicate",
5815
+ message: `Campaign recipient row ${rowNumber} duplicates ${phone}.`,
5816
+ row: rowNumber,
5817
+ value: phone
5818
+ });
5819
+ return;
5820
+ }
5821
+ seenPhones.add(phone);
5822
+ const variables = Object.fromEntries((options.variableColumns ?? []).map((column) => [column, toCampaignScalar(row[column])]).filter(([, value]) => value !== undefined));
5823
+ const metadata = Object.fromEntries((options.metadataColumns ?? []).map((column) => [column, row[column]]).filter(([, value]) => value !== undefined));
5824
+ const id = toCampaignScalar(row[idColumn]);
5825
+ const name = toCampaignScalar(row[nameColumn]);
5826
+ accepted.push({
5827
+ id: typeof id === "string" ? id : undefined,
5828
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
5829
+ name: typeof name === "string" ? name : undefined,
5830
+ phone,
5831
+ variables: Object.keys(variables).length > 0 ? variables : undefined
5832
+ });
5833
+ });
5834
+ return {
5835
+ accepted,
5836
+ duplicates,
5837
+ rejected,
5838
+ total: rows.length
5839
+ };
5840
+ };
5655
5841
  var createVoiceMemoryCampaignStore = () => {
5656
5842
  const campaigns = new Map;
5657
5843
  return {
@@ -5812,24 +5998,28 @@ var buildVoiceCampaignObservabilityReport = async (records, options = {}) => {
5812
5998
  report.stuck.recipients.sort((left, right) => left.updatedAt - right.updatedAt);
5813
5999
  return report;
5814
6000
  };
6001
+ var runtimeAddRecipients = async (store, record, recipients) => {
6002
+ const at = Date.now();
6003
+ record.recipients.push(...recipients.map((recipient) => ({
6004
+ attempts: 0,
6005
+ createdAt: at,
6006
+ id: recipient.id ?? createId2(),
6007
+ metadata: recipient.metadata,
6008
+ name: recipient.name,
6009
+ phone: recipient.phone,
6010
+ status: "pending",
6011
+ updatedAt: at,
6012
+ variables: recipient.variables
6013
+ })));
6014
+ return saveRecord(store, record);
6015
+ };
5815
6016
  var createVoiceCampaign = (options) => {
5816
6017
  const { store } = options;
6018
+ const now = options.now ?? Date.now;
5817
6019
  return {
5818
6020
  addRecipients: async (campaignId, recipients) => {
5819
6021
  const record = await ensureRecord(store, campaignId);
5820
- const at = Date.now();
5821
- record.recipients.push(...recipients.map((recipient) => ({
5822
- attempts: 0,
5823
- createdAt: at,
5824
- id: recipient.id ?? createId2(),
5825
- metadata: recipient.metadata,
5826
- name: recipient.name,
5827
- phone: recipient.phone,
5828
- status: "pending",
5829
- updatedAt: at,
5830
- variables: recipient.variables
5831
- })));
5832
- return saveRecord(store, record);
6022
+ return runtimeAddRecipients(store, record, recipients);
5833
6023
  },
5834
6024
  cancel: async (campaignId) => {
5835
6025
  const record = await ensureRecord(store, campaignId);
@@ -5874,7 +6064,7 @@ var createVoiceCampaign = (options) => {
5874
6064
  return saveRecord(store, record);
5875
6065
  },
5876
6066
  create: async (input) => {
5877
- const at = Date.now();
6067
+ const at = now();
5878
6068
  const campaign = {
5879
6069
  createdAt: at,
5880
6070
  description: input.description,
@@ -5883,6 +6073,7 @@ var createVoiceCampaign = (options) => {
5883
6073
  maxConcurrentAttempts: input.maxConcurrentAttempts ?? 1,
5884
6074
  metadata: input.metadata,
5885
6075
  name: input.name,
6076
+ schedule: input.schedule,
5886
6077
  status: "draft",
5887
6078
  updatedAt: at
5888
6079
  };
@@ -5903,6 +6094,15 @@ var createVoiceCampaign = (options) => {
5903
6094
  return saveRecord(store, record);
5904
6095
  },
5905
6096
  get: async (campaignId) => await store.get(campaignId),
6097
+ importRecipients: async (campaignId, input) => {
6098
+ const result = importVoiceCampaignRecipients(input);
6099
+ const record = await ensureRecord(store, campaignId);
6100
+ const updated = result.accepted.length > 0 ? await runtimeAddRecipients(store, record, result.accepted) : record;
6101
+ return {
6102
+ ...updated,
6103
+ import: result
6104
+ };
6105
+ },
5906
6106
  list: async () => await store.list(),
5907
6107
  pause: async (campaignId) => {
5908
6108
  const record = await ensureRecord(store, campaignId);
@@ -5922,6 +6122,7 @@ var createVoiceCampaign = (options) => {
5922
6122
  const record = await ensureRecord(store, campaignId);
5923
6123
  const result = {
5924
6124
  attempted: 0,
6125
+ blocked: [],
5925
6126
  campaignId,
5926
6127
  errors: [],
5927
6128
  started: []
@@ -5929,9 +6130,53 @@ var createVoiceCampaign = (options) => {
5929
6130
  if (record.campaign.status !== "running") {
5930
6131
  return result;
5931
6132
  }
6133
+ const at = now();
6134
+ const schedule = record.campaign.schedule;
6135
+ if (schedule?.attemptWindow && !isWithinCampaignTimeWindow(schedule.attemptWindow, at)) {
6136
+ result.blocked.push({
6137
+ reason: "outside-attempt-window"
6138
+ });
6139
+ return result;
6140
+ }
6141
+ if (schedule?.quietHours && isWithinCampaignTimeWindow(schedule.quietHours, at)) {
6142
+ result.blocked.push({
6143
+ reason: "quiet-hours"
6144
+ });
6145
+ return result;
6146
+ }
5932
6147
  const capacity = Math.max(0, record.campaign.maxConcurrentAttempts - activeAttemptCount(record));
5933
- const candidates = record.recipients.filter((recipient) => recipient.status === "queued" && recipient.attempts < record.campaign.maxAttempts).slice(0, capacity);
5934
- const at = Date.now();
6148
+ const rateLimit = schedule?.rateLimit;
6149
+ const rateRemaining = rateLimit ? Math.max(0, rateLimit.maxAttempts - record.attempts.filter((attempt) => (attempt.startedAt ?? attempt.createdAt) >= at - rateLimit.windowMs).length) : Number.MAX_SAFE_INTEGER;
6150
+ const availableCapacity = Math.min(capacity, rateRemaining);
6151
+ if (capacity > 0 && rateLimit && rateRemaining === 0) {
6152
+ result.blocked.push({
6153
+ reason: "rate-limit"
6154
+ });
6155
+ }
6156
+ const candidates = [];
6157
+ for (const recipient of record.recipients) {
6158
+ if (candidates.length >= availableCapacity) {
6159
+ break;
6160
+ }
6161
+ if (recipient.status !== "queued" && recipient.status !== "pending") {
6162
+ continue;
6163
+ }
6164
+ if (recipient.attempts >= record.campaign.maxAttempts) {
6165
+ continue;
6166
+ }
6167
+ const backoffMs = getCampaignRetryBackoffMs(schedule?.retryPolicy, recipient.attempts);
6168
+ const lastAttempt = getLastCampaignAttempt(record, recipient.id);
6169
+ const retryAt = lastAttempt && recipient.attempts > 0 ? (lastAttempt.completedAt ?? lastAttempt.updatedAt) + backoffMs : undefined;
6170
+ if (retryAt !== undefined && retryAt > at) {
6171
+ result.blocked.push({
6172
+ reason: "retry-backoff",
6173
+ recipientId: recipient.id,
6174
+ until: retryAt
6175
+ });
6176
+ continue;
6177
+ }
6178
+ candidates.push(recipient);
6179
+ }
5935
6180
  for (const recipient of candidates) {
5936
6181
  const attempt = {
5937
6182
  campaignId,
@@ -5957,12 +6202,13 @@ var createVoiceCampaign = (options) => {
5957
6202
  attempt.externalCallId = dialerResult.externalCallId;
5958
6203
  attempt.metadata = dialerResult.metadata;
5959
6204
  attempt.status = dialerResult.status ?? "running";
5960
- attempt.updatedAt = Date.now();
6205
+ attempt.updatedAt = now();
5961
6206
  } catch (error) {
5962
- attempt.completedAt = Date.now();
6207
+ const failedAt = now();
6208
+ attempt.completedAt = failedAt;
5963
6209
  attempt.error = error instanceof Error ? error.message : String(error);
5964
6210
  attempt.status = "failed";
5965
- attempt.updatedAt = Date.now();
6211
+ attempt.updatedAt = failedAt;
5966
6212
  recipient.error = attempt.error;
5967
6213
  recipient.status = recipient.attempts >= record.campaign.maxAttempts ? "failed" : "pending";
5968
6214
  result.errors.push({
@@ -6279,6 +6525,142 @@ var runVoiceCampaignProof = async (options = {}) => {
6279
6525
  tick
6280
6526
  };
6281
6527
  };
6528
+ var pushCampaignReadinessCheck = (checks, name, condition, details) => {
6529
+ checks.push({
6530
+ details,
6531
+ name,
6532
+ status: condition ? "pass" : "fail"
6533
+ });
6534
+ };
6535
+ var runVoiceCampaignReadinessProof = async (options = {}) => {
6536
+ const checks = [];
6537
+ const store = options.store ?? createVoiceMemoryCampaignStore();
6538
+ let now = Date.UTC(2026, 0, 5, 8, 0, 0);
6539
+ const runtime = createVoiceCampaign({
6540
+ dialer: ({ attempt }) => ({
6541
+ externalCallId: `campaign-readiness-${attempt.id}`,
6542
+ metadata: {
6543
+ mode: "readiness-proof"
6544
+ },
6545
+ status: "running"
6546
+ }),
6547
+ now: () => now,
6548
+ store
6549
+ });
6550
+ const scheduled = await runtime.create({
6551
+ id: `campaign-readiness-schedule-${crypto.randomUUID()}`,
6552
+ maxConcurrentAttempts: 3,
6553
+ name: "Campaign Readiness Schedule Proof",
6554
+ schedule: {
6555
+ attemptWindow: {
6556
+ endHour: 17,
6557
+ startHour: 9
6558
+ },
6559
+ quietHours: {
6560
+ endHour: 13,
6561
+ startHour: 12
6562
+ },
6563
+ rateLimit: {
6564
+ maxAttempts: 1,
6565
+ windowMs: 60000
6566
+ }
6567
+ }
6568
+ });
6569
+ const imported = await runtime.importRecipients(scheduled.campaign.id, {
6570
+ csv: `id,name,phone,consent,segment
6571
+ recipient-1,Ada,+15550001001,yes,alpha
6572
+ recipient-duplicate,Grace,+15550001001,yes,beta
6573
+ recipient-bad,Linus,not-a-phone,yes,gamma
6574
+ recipient-no-consent,Barbara,+15550001004,no,delta`,
6575
+ requireConsent: true,
6576
+ variableColumns: ["segment"]
6577
+ });
6578
+ await runtime.enqueue(scheduled.campaign.id);
6579
+ const windowBlocked = await runtime.tick(scheduled.campaign.id);
6580
+ now = Date.UTC(2026, 0, 5, 12, 30, 0);
6581
+ const quietHours = await runtime.tick(scheduled.campaign.id);
6582
+ now = Date.UTC(2026, 0, 5, 14, 0, 0);
6583
+ const allowed = await runtime.tick(scheduled.campaign.id);
6584
+ const rateLimited = await runtime.tick(scheduled.campaign.id);
6585
+ let retryNow = 1000;
6586
+ const retryRuntime = createVoiceCampaign({
6587
+ dialer: () => {
6588
+ throw new Error("carrier busy");
6589
+ },
6590
+ now: () => retryNow,
6591
+ store
6592
+ });
6593
+ const retry = await retryRuntime.create({
6594
+ id: `campaign-readiness-retry-${crypto.randomUUID()}`,
6595
+ maxAttempts: 2,
6596
+ name: "Campaign Readiness Retry Proof",
6597
+ schedule: {
6598
+ retryPolicy: {
6599
+ backoffMs: 5000
6600
+ }
6601
+ }
6602
+ });
6603
+ await retryRuntime.addRecipients(retry.campaign.id, [
6604
+ {
6605
+ id: "readiness-retry-recipient",
6606
+ phone: "+15550002001"
6607
+ }
6608
+ ]);
6609
+ await retryRuntime.enqueue(retry.campaign.id);
6610
+ const retryInitial = await retryRuntime.tick(retry.campaign.id);
6611
+ retryNow = 3000;
6612
+ const retryBackoff = await retryRuntime.tick(retry.campaign.id);
6613
+ retryNow = 6000;
6614
+ const retryAllowed = await retryRuntime.tick(retry.campaign.id);
6615
+ const finalScheduled = await runtime.get(scheduled.campaign.id);
6616
+ const finalRetry = await retryRuntime.get(retry.campaign.id);
6617
+ if (!finalScheduled || !finalRetry) {
6618
+ throw new Error("Campaign readiness proof did not persist campaign records.");
6619
+ }
6620
+ pushCampaignReadinessCheck(checks, "recipient-import-validation", imported.import.accepted.length === 1 && imported.import.rejected.length === 3, {
6621
+ accepted: imported.import.accepted.length,
6622
+ rejected: imported.import.rejected.length
6623
+ });
6624
+ pushCampaignReadinessCheck(checks, "attempt-window-block", windowBlocked.blocked.some((block) => block.reason === "outside-attempt-window"), {
6625
+ blocked: windowBlocked.blocked
6626
+ });
6627
+ pushCampaignReadinessCheck(checks, "quiet-hours-block", quietHours.blocked.some((block) => block.reason === "quiet-hours"), {
6628
+ blocked: quietHours.blocked
6629
+ });
6630
+ pushCampaignReadinessCheck(checks, "allowed-attempt", allowed.attempted === 1, {
6631
+ attempted: allowed.attempted
6632
+ });
6633
+ pushCampaignReadinessCheck(checks, "rate-limit-block", rateLimited.blocked.some((block) => block.reason === "rate-limit"), {
6634
+ blocked: rateLimited.blocked
6635
+ });
6636
+ pushCampaignReadinessCheck(checks, "retry-backoff-block", retryInitial.attempted === 1 && retryBackoff.blocked.some((block) => block.reason === "retry-backoff"), {
6637
+ blocked: retryBackoff.blocked
6638
+ });
6639
+ pushCampaignReadinessCheck(checks, "retry-to-max-attempts", retryAllowed.attempted === 1 && finalRetry.recipients[0]?.status === "failed", {
6640
+ attempted: retryAllowed.attempted,
6641
+ recipientStatus: finalRetry.recipients[0]?.status
6642
+ });
6643
+ return {
6644
+ campaigns: {
6645
+ retry: finalRetry,
6646
+ scheduled: finalScheduled
6647
+ },
6648
+ checks,
6649
+ generatedAt: Date.now(),
6650
+ import: imported.import,
6651
+ ok: checks.every((check) => check.status === "pass"),
6652
+ proof: "voice-campaign-readiness",
6653
+ ticks: {
6654
+ allowed,
6655
+ quietHours,
6656
+ rateLimited,
6657
+ retryAllowed,
6658
+ retryBackoff,
6659
+ retryInitial,
6660
+ windowBlocked
6661
+ }
6662
+ };
6663
+ };
6282
6664
  var escapeHtml3 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6283
6665
  var renderVoiceCampaignsHTML = (records, options = {}) => {
6284
6666
  const title = options.title ?? "Voice Campaigns";
@@ -6303,13 +6685,13 @@ var createVoiceCampaignRoutes = (options) => {
6303
6685
  const app = new Elysia2({ name: options.name ?? "absolutejs-voice-campaigns" }).get(path, async () => ({
6304
6686
  campaigns: await runtime.list(),
6305
6687
  summary: await runtime.summarize()
6306
- })).get(`${path}/observability`, async () => buildVoiceCampaignObservabilityReport(await runtime.list(), options.observability)).post(path, async ({ request }) => runtime.create(await readJsonBody(request))).get(`${path}/:campaignId`, ({ params }) => runtime.get(params.campaignId)).delete(`${path}/:campaignId`, async ({ params }) => {
6688
+ })).get(`${path}/observability`, async () => buildVoiceCampaignObservabilityReport(await runtime.list(), options.observability)).get(`${path}/readiness-proof`, () => runVoiceCampaignReadinessProof()).post(path, async ({ request }) => runtime.create(await readJsonBody(request))).get(`${path}/:campaignId`, ({ params }) => runtime.get(params.campaignId)).delete(`${path}/:campaignId`, async ({ params }) => {
6307
6689
  await runtime.remove(params.campaignId);
6308
6690
  return { ok: true };
6309
6691
  }).post(`${path}/:campaignId/recipients`, async ({ params, request }) => {
6310
6692
  const body = await readJsonBody(request);
6311
6693
  return runtime.addRecipients(params.campaignId, body.recipients ?? []);
6312
- }).post(`${path}/:campaignId/enqueue`, ({ params }) => runtime.enqueue(params.campaignId)).post(`${path}/:campaignId/pause`, ({ params }) => runtime.pause(params.campaignId)).post(`${path}/:campaignId/resume`, ({ params }) => runtime.resume(params.campaignId)).post(`${path}/:campaignId/cancel`, ({ params }) => runtime.cancel(params.campaignId)).post(`${path}/:campaignId/tick`, ({ params }) => runtime.tick(params.campaignId)).post(`${path}/:campaignId/attempts/:attemptId/result`, async ({
6694
+ }).post(`${path}/:campaignId/recipients/import`, async ({ params, request }) => runtime.importRecipients(params.campaignId, await readJsonBody(request))).post(`${path}/:campaignId/enqueue`, ({ params }) => runtime.enqueue(params.campaignId)).post(`${path}/:campaignId/pause`, ({ params }) => runtime.pause(params.campaignId)).post(`${path}/:campaignId/resume`, ({ params }) => runtime.resume(params.campaignId)).post(`${path}/:campaignId/cancel`, ({ params }) => runtime.cancel(params.campaignId)).post(`${path}/:campaignId/tick`, ({ params }) => runtime.tick(params.campaignId)).post(`${path}/:campaignId/attempts/:attemptId/result`, async ({
6313
6695
  params,
6314
6696
  request
6315
6697
  }) => runtime.completeAttempt(params.campaignId, params.attemptId, await readJsonBody(request)));
@@ -15189,6 +15571,308 @@ var createVoiceLiveLatencyRoutes = (options) => {
15189
15571
  }
15190
15572
  return routes;
15191
15573
  };
15574
+ // src/latencySlo.ts
15575
+ var DEFAULT_WARN_AFTER_MS2 = 1800;
15576
+ var DEFAULT_FAIL_AFTER_MS2 = 3200;
15577
+ var STAGE_LABELS = {
15578
+ assistant_text_to_tts_send: "Assistant text to TTS send",
15579
+ barge_in_stop: "Barge-in stop latency",
15580
+ commit_to_assistant_text: "Commit to assistant text",
15581
+ final_to_commit: "Final transcript to commit",
15582
+ live_latency: "Browser live latency",
15583
+ provider_llm: "LLM provider latency",
15584
+ provider_stt: "STT provider latency",
15585
+ provider_tts: "TTS provider latency",
15586
+ speech_to_commit: "Speech detected to commit",
15587
+ tts_send_duration: "TTS send duration",
15588
+ tts_to_first_audio: "TTS to first audio"
15589
+ };
15590
+ var TRACE_TYPES = [
15591
+ "assistant.run",
15592
+ "client.barge_in",
15593
+ "client.live_latency",
15594
+ "turn.transcript",
15595
+ "turn_latency.stage"
15596
+ ];
15597
+ var getNumber6 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
15598
+ var getString11 = (value) => typeof value === "string" && value.trim() ? value : undefined;
15599
+ var percentile2 = (values, percentileValue) => {
15600
+ if (values.length === 0) {
15601
+ return;
15602
+ }
15603
+ const sorted = [...values].sort((left, right) => left - right);
15604
+ const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(percentileValue / 100 * sorted.length) - 1));
15605
+ return Math.round(sorted[index] ?? 0);
15606
+ };
15607
+ var average = (values) => values.length === 0 ? undefined : Math.round(values.reduce((total, value) => total + value, 0) / values.length);
15608
+ var resolveBudget = (stage, options) => ({
15609
+ failAfterMs: options.budgets?.[stage]?.failAfterMs ?? options.failAfterMs ?? DEFAULT_FAIL_AFTER_MS2,
15610
+ warnAfterMs: options.budgets?.[stage]?.warnAfterMs ?? options.warnAfterMs ?? DEFAULT_WARN_AFTER_MS2
15611
+ });
15612
+ var statusForLatency = (latencyMs, budget) => latencyMs > budget.failAfterMs ? "fail" : budget.warnAfterMs !== undefined && latencyMs > budget.warnAfterMs ? "warn" : "pass";
15613
+ var stageMeasurement = (input) => {
15614
+ if (input.latencyMs === undefined) {
15615
+ return;
15616
+ }
15617
+ return {
15618
+ at: input.at,
15619
+ latencyMs: Math.max(0, Math.round(input.latencyMs)),
15620
+ label: STAGE_LABELS[input.stage],
15621
+ provider: input.provider,
15622
+ sessionId: input.sessionId,
15623
+ stage: input.stage,
15624
+ status: statusForLatency(Math.max(0, input.latencyMs), input.budget),
15625
+ turnId: input.turnId
15626
+ };
15627
+ };
15628
+ var providerStageForEvent = (event) => {
15629
+ if (event.type === "assistant.run") {
15630
+ return "provider_llm";
15631
+ }
15632
+ if (event.type === "turn.transcript") {
15633
+ return "provider_stt";
15634
+ }
15635
+ const kind = getString11(event.payload.providerKind) ?? getString11(event.payload.kind) ?? getString11(event.payload.lane);
15636
+ if (kind === "llm" || kind === "model") {
15637
+ return "provider_llm";
15638
+ }
15639
+ if (kind === "stt" || kind === "transcription") {
15640
+ return "provider_stt";
15641
+ }
15642
+ if (kind === "tts" || kind === "speech") {
15643
+ return "provider_tts";
15644
+ }
15645
+ return;
15646
+ };
15647
+ var eventElapsedMs = (event) => getNumber6(event.payload.elapsedMs) ?? getNumber6(event.payload.latencyMs) ?? getNumber6(event.payload.durationMs);
15648
+ var collectTraceStageMeasurements = (events, options) => {
15649
+ const grouped = new Map;
15650
+ for (const event of events) {
15651
+ if (event.type !== "turn_latency.stage" || !event.turnId) {
15652
+ continue;
15653
+ }
15654
+ const stage = getString11(event.payload.stage);
15655
+ if (!stage) {
15656
+ continue;
15657
+ }
15658
+ const key = `${event.sessionId}:${event.turnId}`;
15659
+ const stages = grouped.get(key) ?? new Map;
15660
+ const previous = stages.get(stage);
15661
+ if (!previous || event.at < previous.at) {
15662
+ stages.set(stage, event);
15663
+ }
15664
+ grouped.set(key, stages);
15665
+ }
15666
+ const measurements = [];
15667
+ for (const [key, stages] of grouped) {
15668
+ const [sessionId, turnId] = key.split(":");
15669
+ if (!sessionId || !turnId) {
15670
+ continue;
15671
+ }
15672
+ const speechDetected = stages.get("speech_detected")?.at;
15673
+ const finalTranscript = stages.get("final_transcript")?.at;
15674
+ const turnCommitted = stages.get("turn_committed")?.at;
15675
+ const assistantTextStarted = stages.get("assistant_text_started")?.at;
15676
+ const ttsSendStarted = stages.get("tts_send_started")?.at;
15677
+ const ttsSendCompleted = stages.get("tts_send_completed")?.at;
15678
+ const assistantAudioReceived = stages.get("assistant_audio_received")?.at;
15679
+ const candidates = [
15680
+ {
15681
+ at: turnCommitted ?? 0,
15682
+ budget: resolveBudget("speech_to_commit", options),
15683
+ latencyMs: speechDetected === undefined || turnCommitted === undefined ? undefined : turnCommitted - speechDetected,
15684
+ required: true,
15685
+ sessionId,
15686
+ stage: "speech_to_commit",
15687
+ turnId
15688
+ },
15689
+ {
15690
+ at: turnCommitted ?? 0,
15691
+ budget: resolveBudget("final_to_commit", options),
15692
+ latencyMs: finalTranscript === undefined || turnCommitted === undefined ? undefined : turnCommitted - finalTranscript,
15693
+ required: true,
15694
+ sessionId,
15695
+ stage: "final_to_commit",
15696
+ turnId
15697
+ },
15698
+ {
15699
+ at: assistantTextStarted ?? 0,
15700
+ budget: resolveBudget("commit_to_assistant_text", options),
15701
+ latencyMs: turnCommitted === undefined || assistantTextStarted === undefined ? undefined : assistantTextStarted - turnCommitted,
15702
+ required: true,
15703
+ sessionId,
15704
+ stage: "commit_to_assistant_text",
15705
+ turnId
15706
+ },
15707
+ {
15708
+ at: ttsSendStarted ?? 0,
15709
+ budget: resolveBudget("assistant_text_to_tts_send", options),
15710
+ latencyMs: assistantTextStarted === undefined || ttsSendStarted === undefined ? undefined : ttsSendStarted - assistantTextStarted,
15711
+ required: true,
15712
+ sessionId,
15713
+ stage: "assistant_text_to_tts_send",
15714
+ turnId
15715
+ },
15716
+ {
15717
+ at: ttsSendCompleted ?? 0,
15718
+ budget: resolveBudget("tts_send_duration", options),
15719
+ latencyMs: ttsSendStarted === undefined || ttsSendCompleted === undefined ? undefined : ttsSendCompleted - ttsSendStarted,
15720
+ required: true,
15721
+ sessionId,
15722
+ stage: "tts_send_duration",
15723
+ turnId
15724
+ },
15725
+ {
15726
+ at: assistantAudioReceived ?? 0,
15727
+ budget: resolveBudget("tts_to_first_audio", options),
15728
+ latencyMs: ttsSendCompleted === undefined || assistantAudioReceived === undefined ? undefined : assistantAudioReceived - ttsSendCompleted,
15729
+ required: true,
15730
+ sessionId,
15731
+ stage: "tts_to_first_audio",
15732
+ turnId
15733
+ }
15734
+ ];
15735
+ for (const candidate of candidates) {
15736
+ const measurement = stageMeasurement(candidate);
15737
+ if (measurement) {
15738
+ measurements.push(measurement);
15739
+ }
15740
+ }
15741
+ }
15742
+ return measurements;
15743
+ };
15744
+ var collectDirectMeasurements = (events, options) => {
15745
+ const measurements = [];
15746
+ for (const event of events) {
15747
+ if (event.type === "client.live_latency") {
15748
+ const stage = "live_latency";
15749
+ const measurement = stageMeasurement({
15750
+ at: event.at,
15751
+ budget: resolveBudget(stage, options),
15752
+ latencyMs: eventElapsedMs(event),
15753
+ sessionId: event.sessionId,
15754
+ stage,
15755
+ turnId: event.turnId
15756
+ });
15757
+ if (measurement) {
15758
+ measurements.push(measurement);
15759
+ }
15760
+ continue;
15761
+ }
15762
+ if (event.type === "client.barge_in") {
15763
+ const stage = "barge_in_stop";
15764
+ const measurement = stageMeasurement({
15765
+ at: event.at,
15766
+ budget: resolveBudget(stage, options),
15767
+ latencyMs: eventElapsedMs(event),
15768
+ sessionId: event.sessionId,
15769
+ stage,
15770
+ turnId: event.turnId
15771
+ });
15772
+ if (measurement) {
15773
+ measurements.push(measurement);
15774
+ }
15775
+ continue;
15776
+ }
15777
+ const providerStage = providerStageForEvent(event);
15778
+ if (providerStage) {
15779
+ const measurement = stageMeasurement({
15780
+ at: event.at,
15781
+ budget: resolveBudget(providerStage, options),
15782
+ latencyMs: eventElapsedMs(event),
15783
+ provider: getString11(event.payload.provider),
15784
+ sessionId: event.sessionId,
15785
+ stage: providerStage,
15786
+ turnId: event.turnId
15787
+ });
15788
+ if (measurement) {
15789
+ measurements.push(measurement);
15790
+ }
15791
+ }
15792
+ }
15793
+ return measurements;
15794
+ };
15795
+ var summarizeStage = (stage, measurements, options) => {
15796
+ const stageMeasurements = measurements.filter((measurement) => measurement.stage === stage);
15797
+ const latencies = stageMeasurements.map((measurement) => measurement.latencyMs);
15798
+ const failed = stageMeasurements.filter((measurement) => measurement.status === "fail").length;
15799
+ const warnings = stageMeasurements.filter((measurement) => measurement.status === "warn").length;
15800
+ return {
15801
+ averageMs: average(latencies),
15802
+ budget: resolveBudget(stage, options),
15803
+ failed,
15804
+ label: STAGE_LABELS[stage],
15805
+ maxMs: latencies.length > 0 ? Math.max(...latencies) : undefined,
15806
+ measurements: stageMeasurements,
15807
+ p50Ms: percentile2(latencies, 50),
15808
+ p95Ms: percentile2(latencies, 95),
15809
+ stage,
15810
+ status: stageMeasurements.length === 0 ? "empty" : failed > 0 ? "fail" : warnings > 0 ? "warn" : "pass",
15811
+ total: stageMeasurements.length,
15812
+ warnings
15813
+ };
15814
+ };
15815
+ var buildVoiceLatencySLOGate = async (options) => {
15816
+ const events = options.events ?? await options.store?.list({
15817
+ limit: options.limit ?? 1000,
15818
+ type: TRACE_TYPES
15819
+ }) ?? [];
15820
+ const measurements = [
15821
+ ...collectTraceStageMeasurements(events, options),
15822
+ ...collectDirectMeasurements(events, options)
15823
+ ].sort((left, right) => right.at - left.at);
15824
+ const stageKeys = new Set([
15825
+ ...Object.keys(options.budgets ?? {}),
15826
+ ...measurements.map((measurement) => measurement.stage)
15827
+ ]);
15828
+ const stages = [...stageKeys].map((stage) => summarizeStage(stage, measurements, options)).sort((left, right) => left.label.localeCompare(right.label));
15829
+ const failed = measurements.filter((measurement) => measurement.status === "fail").length;
15830
+ const warnings = measurements.filter((measurement) => measurement.status === "warn").length;
15831
+ return {
15832
+ checkedAt: Date.now(),
15833
+ failed,
15834
+ measurements,
15835
+ stages,
15836
+ status: measurements.length === 0 ? "empty" : failed > 0 ? "fail" : warnings > 0 ? "warn" : "pass",
15837
+ total: measurements.length,
15838
+ warnings
15839
+ };
15840
+ };
15841
+ var assertVoiceLatencySLOGate = async (options) => {
15842
+ const report = await buildVoiceLatencySLOGate(options);
15843
+ if (report.status === "fail") {
15844
+ const error = new Error(`Voice latency SLO gate failed with ${report.failed} failed measurement(s).`);
15845
+ error.report = report;
15846
+ throw error;
15847
+ }
15848
+ return report;
15849
+ };
15850
+ var renderVoiceLatencySLOMarkdown = (report, options = {}) => {
15851
+ const title = options.title ?? "Voice Latency SLO Gate";
15852
+ const rows = report.stages.map((stage) => `| ${stage.label} | ${stage.status} | ${stage.total} | ${stage.p50Ms ?? "n/a"} | ${stage.p95Ms ?? "n/a"} | ${stage.budget.warnAfterMs ?? "n/a"} | ${stage.budget.failAfterMs} |`).join(`
15853
+ `);
15854
+ const failures = report.measurements.filter((measurement) => measurement.status === "fail").map((measurement) => `- ${measurement.label}: ${measurement.latencyMs}ms in ${measurement.sessionId}${measurement.turnId ? `/${measurement.turnId}` : ""}${measurement.provider ? ` via ${measurement.provider}` : ""}`).join(`
15855
+ `);
15856
+ return `# ${title}
15857
+
15858
+ Status: ${report.status}
15859
+
15860
+ Total measurements: ${report.total}
15861
+ Warnings: ${report.warnings}
15862
+ Failures: ${report.failed}
15863
+
15864
+ | Stage | Status | Samples | p50 ms | p95 ms | Warn ms | Fail ms |
15865
+ | --- | --- | ---: | ---: | ---: | ---: | ---: |
15866
+ ${rows || "| No latency measurements | empty | 0 | n/a | n/a | n/a | n/a |"}
15867
+
15868
+ ${failures ? `## Failures
15869
+
15870
+ ${failures}
15871
+ ` : `## Failures
15872
+
15873
+ None.
15874
+ `}`;
15875
+ };
15192
15876
  // src/turnQuality.ts
15193
15877
  import { Elysia as Elysia24 } from "elysia";
15194
15878
  var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
@@ -20237,8 +20921,8 @@ var createVoiceProviderCapabilityRoutes = (options) => {
20237
20921
  // src/resilienceRoutes.ts
20238
20922
  import { Elysia as Elysia33 } from "elysia";
20239
20923
  var escapeHtml34 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
20240
- var getString11 = (value) => typeof value === "string" ? value : undefined;
20241
- var getNumber6 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
20924
+ var getString12 = (value) => typeof value === "string" ? value : undefined;
20925
+ var getNumber7 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
20242
20926
  var getBoolean2 = (value) => value === true;
20243
20927
  var isProviderStatus2 = (value) => value === "error" || value === "fallback" || value === "success";
20244
20928
  var listVoiceRoutingEvents = (events) => {
@@ -20247,27 +20931,27 @@ var listVoiceRoutingEvents = (events) => {
20247
20931
  if (event.type !== "session.error") {
20248
20932
  continue;
20249
20933
  }
20250
- const provider = getString11(event.payload.provider);
20934
+ const provider = getString12(event.payload.provider);
20251
20935
  const providerStatus = isProviderStatus2(event.payload.providerStatus) ? event.payload.providerStatus : undefined;
20252
20936
  if (!provider || !providerStatus) {
20253
20937
  continue;
20254
20938
  }
20255
- const kind = getString11(event.payload.kind);
20939
+ const kind = getString12(event.payload.kind);
20256
20940
  routingEvents.push({
20257
20941
  at: event.at,
20258
- attempt: getNumber6(event.payload.attempt),
20259
- elapsedMs: getNumber6(event.payload.elapsedMs),
20260
- error: getString11(event.payload.error),
20261
- fallbackProvider: getString11(event.payload.fallbackProvider),
20942
+ attempt: getNumber7(event.payload.attempt),
20943
+ elapsedMs: getNumber7(event.payload.elapsedMs),
20944
+ error: getString12(event.payload.error),
20945
+ fallbackProvider: getString12(event.payload.fallbackProvider),
20262
20946
  kind: kind === "stt" || kind === "tts" ? kind : "llm",
20263
- latencyBudgetMs: getNumber6(event.payload.latencyBudgetMs),
20264
- operation: getString11(event.payload.operation),
20947
+ latencyBudgetMs: getNumber7(event.payload.latencyBudgetMs),
20948
+ operation: getString12(event.payload.operation),
20265
20949
  provider,
20266
- routing: getString11(event.payload.routing),
20267
- selectedProvider: getString11(event.payload.selectedProvider),
20950
+ routing: getString12(event.payload.routing),
20951
+ selectedProvider: getString12(event.payload.selectedProvider),
20268
20952
  sessionId: event.sessionId,
20269
20953
  status: providerStatus,
20270
- suppressionRemainingMs: getNumber6(event.payload.suppressionRemainingMs),
20954
+ suppressionRemainingMs: getNumber7(event.payload.suppressionRemainingMs),
20271
20955
  timedOut: getBoolean2(event.payload.timedOut),
20272
20956
  turnId: event.turnId
20273
20957
  });
@@ -20764,6 +21448,7 @@ var readinessGateCodes = {
20764
21448
  "Audit evidence": "voice.readiness.audit_evidence",
20765
21449
  "Audit sink delivery": "voice.readiness.audit_sink_delivery",
20766
21450
  "Barge-in interruption proof": "voice.readiness.barge_in_interruption",
21451
+ "Campaign readiness proof": "voice.readiness.campaign_readiness",
20767
21452
  "Carrier readiness": "voice.readiness.carrier_readiness",
20768
21453
  "Delivery runtime": "voice.readiness.delivery_runtime",
20769
21454
  "Handoff delivery": "voice.readiness.handoff_delivery",
@@ -20877,6 +21562,12 @@ var resolveBargeInReports = async (options, input) => {
20877
21562
  }
20878
21563
  return typeof options.bargeInReports === "function" ? await options.bargeInReports(input) : options.bargeInReports;
20879
21564
  };
21565
+ var resolveCampaignReadiness = async (options, input) => {
21566
+ if (options.campaignReadiness === false || options.campaignReadiness === undefined) {
21567
+ return;
21568
+ }
21569
+ return typeof options.campaignReadiness === "function" ? await options.campaignReadiness(input) : options.campaignReadiness;
21570
+ };
20880
21571
  var resolveProofSources = async (options, input) => {
20881
21572
  if (options.proofSources === false || options.proofSources === undefined) {
20882
21573
  return;
@@ -21097,8 +21788,8 @@ var summarizeLiveLatency = (events, options) => {
21097
21788
  warnings
21098
21789
  };
21099
21790
  };
21100
- var getString12 = (value) => typeof value === "string" ? value : undefined;
21101
- var getNumber7 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
21791
+ var getString13 = (value) => typeof value === "string" ? value : undefined;
21792
+ var getNumber8 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
21102
21793
  var voiceOperationsRecordHref = (base, sessionId) => {
21103
21794
  const encoded = encodeURIComponent(sessionId);
21104
21795
  if (base.includes(":sessionId")) {
@@ -21109,7 +21800,7 @@ var voiceOperationsRecordHref = (base, sessionId) => {
21109
21800
  var buildOperationsRecordLinks = (input) => {
21110
21801
  const failedSessionSet = new Set(input.failedSessionIds);
21111
21802
  const providerErrors = input.events.filter((event) => event.type === "session.error" && (event.payload.providerStatus === "error" || typeof event.payload.error === "string")).map((event) => ({
21112
- detail: getString12(event.payload.error),
21803
+ detail: getString13(event.payload.error),
21113
21804
  href: voiceOperationsRecordHref(input.base, event.sessionId),
21114
21805
  label: "Open provider error operations record",
21115
21806
  sessionId: event.sessionId,
@@ -21117,7 +21808,7 @@ var buildOperationsRecordLinks = (input) => {
21117
21808
  }));
21118
21809
  const failingLatency = input.events.filter((event) => event.type === "client.live_latency").map((event) => ({
21119
21810
  event,
21120
- latencyMs: getNumber7(event.payload.latencyMs) ?? getNumber7(event.payload.elapsedMs)
21811
+ latencyMs: getNumber8(event.payload.latencyMs) ?? getNumber8(event.payload.elapsedMs)
21121
21812
  })).filter((entry) => entry.latencyMs !== undefined && entry.latencyMs > input.liveLatencyWarnAfterMs).map(({ event, latencyMs }) => ({
21122
21813
  detail: `${latencyMs}ms live latency`,
21123
21814
  href: voiceOperationsRecordHref(input.base, event.sessionId),
@@ -21163,6 +21854,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21163
21854
  phoneAgentSmokes,
21164
21855
  reconnectContracts,
21165
21856
  bargeInReports,
21857
+ campaignReadiness,
21166
21858
  proofSources
21167
21859
  ] = await Promise.all([
21168
21860
  evaluateVoiceQuality({ events }),
@@ -21195,6 +21887,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21195
21887
  resolvePhoneAgentSmokes(options, { query, request }),
21196
21888
  resolveReconnectContracts(options, { query, request }),
21197
21889
  resolveBargeInReports(options, { query, request }),
21890
+ resolveCampaignReadiness(options, { query, request }),
21198
21891
  resolveProofSources(options, { query, request })
21199
21892
  ]);
21200
21893
  const deliveryRuntime = summarizeDeliveryRuntime(deliveryRuntimeSummary);
@@ -21381,6 +22074,12 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21381
22074
  total: bargeInReports.reduce((total, report) => total + report.total, 0),
21382
22075
  warnings: bargeInReports.filter((report) => report.status === "warn").length
21383
22076
  } : undefined;
22077
+ const campaignReadinessSummary = campaignReadiness ? {
22078
+ failed: campaignReadiness.checks.filter((check) => check.status !== "pass").length,
22079
+ passed: campaignReadiness.checks.filter((check) => check.status === "pass").length,
22080
+ status: campaignReadiness.ok ? "pass" : "fail",
22081
+ total: campaignReadiness.checks.length
22082
+ } : undefined;
21384
22083
  if (agentSquadContractSummary) {
21385
22084
  checks.push({
21386
22085
  detail: agentSquadContractSummary.status === "pass" ? `${agentSquadContractSummary.passed} agent squad contract(s) are passing.` : agentSquadContractSummary.total === 0 ? "No agent squad contracts are configured." : `${agentSquadContractSummary.failed} agent squad contract(s) failed.`,
@@ -21502,6 +22201,24 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21502
22201
  ]
21503
22202
  });
21504
22203
  }
22204
+ if (campaignReadinessSummary) {
22205
+ const failedChecks = campaignReadiness?.checks.filter((check) => check.status !== "pass").map((check) => check.name);
22206
+ checks.push({
22207
+ detail: campaignReadinessSummary.status === "pass" ? `${campaignReadinessSummary.passed} campaign readiness check(s) are passing without live dialing.` : failedChecks && failedChecks.length > 0 ? `Campaign readiness failed: ${failedChecks.join(", ")}.` : "Campaign readiness proof failed.",
22208
+ href: options.links?.campaignReadiness ?? "/api/voice/campaigns/readiness-proof",
22209
+ label: "Campaign readiness proof",
22210
+ proofSource: proofSource("campaignReadiness", "campaigns"),
22211
+ status: campaignReadinessSummary.status,
22212
+ value: `${campaignReadinessSummary.passed}/${campaignReadinessSummary.total}`,
22213
+ actions: campaignReadinessSummary.status === "pass" ? [] : [
22214
+ {
22215
+ description: "Open campaign readiness proof and inspect import, scheduling, rate-limit, and retry checks.",
22216
+ href: options.links?.campaignReadiness ?? "/api/voice/campaigns/readiness-proof",
22217
+ label: "Open campaign proof"
22218
+ }
22219
+ ]
22220
+ });
22221
+ }
21505
22222
  if (audit) {
21506
22223
  const missingLabels = audit.missing.map((requirement) => requirement.label ?? requirement.type);
21507
22224
  checks.push({
@@ -21611,6 +22328,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21611
22328
  audit: "/audit",
21612
22329
  auditDeliveries: "/audit",
21613
22330
  bargeIn: "/barge-in",
22331
+ campaignReadiness: "/api/voice/campaigns/readiness-proof",
21614
22332
  carriers: "/carriers",
21615
22333
  deliveryRuntime: "/delivery-runtime",
21616
22334
  handoffs: "/handoffs",
@@ -21637,6 +22355,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21637
22355
  audit,
21638
22356
  auditDeliveries,
21639
22357
  bargeIn: bargeInSummary,
22358
+ campaignReadiness: campaignReadinessSummary,
21640
22359
  carriers: carrierSummary,
21641
22360
  deliveryRuntime,
21642
22361
  handoffs: {
@@ -21762,6 +22481,7 @@ var profileSurfaceLabels = {
21762
22481
  },
21763
22482
  "phone-agent": {
21764
22483
  auditDeliveries: "audit delivery queue configured",
22484
+ campaignReadiness: "campaign readiness proof configured",
21765
22485
  carriers: "carrier readiness configured",
21766
22486
  deliveryRuntime: "delivery runtime configured",
21767
22487
  phoneAgentSmokes: "phone-agent smoke proof configured",
@@ -21785,6 +22505,7 @@ var profileRequiredKeys = {
21785
22505
  "phone-agent": [
21786
22506
  "carriers",
21787
22507
  "phoneAgentSmokes",
22508
+ "campaignReadiness",
21788
22509
  "providerRoutingContracts",
21789
22510
  "auditDeliveries",
21790
22511
  "traceDeliveries",
@@ -21797,6 +22518,7 @@ var configuredProfileKeys = (options) => {
21797
22518
  "audit",
21798
22519
  "auditDeliveries",
21799
22520
  "bargeInReports",
22521
+ "campaignReadiness",
21800
22522
  "carriers",
21801
22523
  "deliveryRuntime",
21802
22524
  "opsActionHistory",
@@ -21893,6 +22615,12 @@ var profileExplanation = (profile, options, links) => {
21893
22615
  key: "phoneAgentSmokes",
21894
22616
  label: "Phone agent smoke"
21895
22617
  },
22618
+ {
22619
+ configured: isConfigured(options.campaignReadiness),
22620
+ href: links.campaignReadiness,
22621
+ key: "campaignReadiness",
22622
+ label: "Campaign readiness proof"
22623
+ },
21896
22624
  {
21897
22625
  configured: true,
21898
22626
  href: links.handoffs,
@@ -21988,6 +22716,7 @@ var createVoiceReadinessProfile = (profile, options = {}) => {
21988
22716
  if (profile === "phone-agent") {
21989
22717
  const links2 = mergeLinks({
21990
22718
  auditDeliveries: "/audit/deliveries",
22719
+ campaignReadiness: "/api/voice/campaigns/readiness-proof",
21991
22720
  carriers: "/carriers",
21992
22721
  deliveryRuntime: "/delivery-runtime",
21993
22722
  handoffs: "/handoffs",
@@ -21999,6 +22728,7 @@ var createVoiceReadinessProfile = (profile, options = {}) => {
21999
22728
  }, options.links);
22000
22729
  return withDefined({
22001
22730
  auditDeliveries: options.auditDeliveries,
22731
+ campaignReadiness: options.campaignReadiness,
22002
22732
  carriers: options.carriers,
22003
22733
  deliveryRuntime: options.deliveryRuntime,
22004
22734
  gate: options.gate,
@@ -22551,11 +23281,11 @@ import { Elysia as Elysia38 } from "elysia";
22551
23281
  // src/traceTimeline.ts
22552
23282
  import { Elysia as Elysia37 } from "elysia";
22553
23283
  var escapeHtml38 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
22554
- var getString13 = (value) => typeof value === "string" && value.trim() ? value : undefined;
22555
- var getNumber8 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
23284
+ var getString14 = (value) => typeof value === "string" && value.trim() ? value : undefined;
23285
+ var getNumber9 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
22556
23286
  var firstString3 = (payload, keys) => {
22557
23287
  for (const key of keys) {
22558
- const value = getString13(payload[key]);
23288
+ const value = getString14(payload[key]);
22559
23289
  if (value) {
22560
23290
  return value;
22561
23291
  }
@@ -22564,7 +23294,7 @@ var firstString3 = (payload, keys) => {
22564
23294
  };
22565
23295
  var firstNumber3 = (payload, keys) => {
22566
23296
  for (const key of keys) {
22567
- const value = getNumber8(payload[key]);
23297
+ const value = getNumber9(payload[key]);
22568
23298
  if (value !== undefined) {
22569
23299
  return value;
22570
23300
  }
@@ -22584,7 +23314,7 @@ var eventStatus = (event) => firstString3(event.payload, [
22584
23314
  "type",
22585
23315
  "reason"
22586
23316
  ]);
22587
- var eventElapsedMs = (event) => firstNumber3(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
23317
+ var eventElapsedMs2 = (event) => firstNumber3(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
22588
23318
  var timelineLabel = (event) => {
22589
23319
  switch (event.type) {
22590
23320
  case "call.lifecycle":
@@ -22592,15 +23322,15 @@ var timelineLabel = (event) => {
22592
23322
  case "turn.transcript":
22593
23323
  return event.payload.isFinal === true ? "Final transcript" : "Partial transcript";
22594
23324
  case "turn.committed":
22595
- return `Committed turn${getString13(event.payload.reason) ? ` (${getString13(event.payload.reason)})` : ""}`;
23325
+ return `Committed turn${getString14(event.payload.reason) ? ` (${getString14(event.payload.reason)})` : ""}`;
22596
23326
  case "turn.assistant":
22597
23327
  return "Assistant reply";
22598
23328
  case "agent.model":
22599
23329
  return `Model call${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
22600
23330
  case "agent.tool":
22601
- return `Tool ${getString13(event.payload.toolName) ?? "call"}`;
23331
+ return `Tool ${getString14(event.payload.toolName) ?? "call"}`;
22602
23332
  case "agent.handoff":
22603
- return `Agent handoff${getString13(event.payload.targetAgentId) ? ` to ${getString13(event.payload.targetAgentId)}` : ""}`;
23333
+ return `Agent handoff${getString14(event.payload.targetAgentId) ? ` to ${getString14(event.payload.targetAgentId)}` : ""}`;
22604
23334
  case "assistant.run":
22605
23335
  return `Assistant run${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
22606
23336
  case "assistant.guardrail":
@@ -22608,13 +23338,13 @@ var timelineLabel = (event) => {
22608
23338
  case "call.handoff":
22609
23339
  return `Call handoff ${eventStatus(event) ?? ""}`.trim();
22610
23340
  case "client.live_latency":
22611
- return `Live latency${eventElapsedMs(event) !== undefined ? ` ${eventElapsedMs(event)}ms` : ""}`;
23341
+ return `Live latency${eventElapsedMs2(event) !== undefined ? ` ${eventElapsedMs2(event)}ms` : ""}`;
22612
23342
  case "session.error":
22613
- return `Error${getString13(event.payload.error) ? `: ${getString13(event.payload.error)}` : ""}`;
23343
+ return `Error${getString14(event.payload.error) ? `: ${getString14(event.payload.error)}` : ""}`;
22614
23344
  case "turn.cost":
22615
23345
  return "Cost telemetry";
22616
23346
  case "turn_latency.stage":
22617
- return `Latency ${getString13(event.payload.stage) ?? "stage"}`;
23347
+ return `Latency ${getString14(event.payload.stage) ?? "stage"}`;
22618
23348
  case "workflow.contract":
22619
23349
  return `Workflow contract ${eventStatus(event) ?? ""}`.trim();
22620
23350
  default:
@@ -22646,7 +23376,7 @@ var summarizeProviders = (events) => {
22646
23376
  }
22647
23377
  const entry = getEntry(provider);
22648
23378
  const status = eventStatus(event);
22649
- const elapsedMs = eventElapsedMs(event);
23379
+ const elapsedMs = eventElapsedMs2(event);
22650
23380
  entry.eventCount += 1;
22651
23381
  if (elapsedMs !== undefined) {
22652
23382
  entry.elapsed.push(elapsedMs);
@@ -22692,7 +23422,7 @@ var summarizeVoiceTraceTimeline = (events, options = {}) => {
22692
23422
  evaluation,
22693
23423
  events: sorted.map((event) => ({
22694
23424
  at: event.at,
22695
- elapsedMs: eventElapsedMs(event),
23425
+ elapsedMs: eventElapsedMs2(event),
22696
23426
  id: event.id,
22697
23427
  label: timelineLabel(event),
22698
23428
  offsetMs: Math.max(0, event.at - startedAt),
@@ -22807,26 +23537,32 @@ var createVoiceTraceTimelineRoutes = (options) => {
22807
23537
  };
22808
23538
 
22809
23539
  // src/operationsRecord.ts
22810
- var getString14 = (value) => typeof value === "string" ? value : undefined;
22811
- var getNumber9 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
23540
+ var getString15 = (value) => typeof value === "string" ? value : undefined;
23541
+ var getNumber10 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
22812
23542
  var countOutcome = (events, outcome) => events.filter((event) => event.outcome === outcome).length;
23543
+ var matchesSessionScopedId = (id, sessionId) => id === sessionId || id.startsWith(`${sessionId}:`);
23544
+ var hasPayloadValue = (payload, key, values) => {
23545
+ const value = payload[key];
23546
+ return typeof value === "string" && values.has(value);
23547
+ };
23548
+ var countIntegrationDeliveryStatus = (events, status) => events.filter((event) => event.deliveryStatus === status).length;
22813
23549
  var toHandoff = (event) => ({
22814
23550
  at: event.at,
22815
- fromAgentId: getString14(event.payload.fromAgentId),
23551
+ fromAgentId: getString15(event.payload.fromAgentId),
22816
23552
  metadata: event.payload.metadata && typeof event.payload.metadata === "object" && !Array.isArray(event.payload.metadata) ? event.payload.metadata : undefined,
22817
- reason: getString14(event.payload.reason),
22818
- status: getString14(event.payload.status),
22819
- summary: getString14(event.payload.summary),
22820
- targetAgentId: getString14(event.payload.targetAgentId),
23553
+ reason: getString15(event.payload.reason),
23554
+ status: getString15(event.payload.status),
23555
+ summary: getString15(event.payload.summary),
23556
+ targetAgentId: getString15(event.payload.targetAgentId),
22821
23557
  turnId: event.turnId
22822
23558
  });
22823
23559
  var toTool = (event) => ({
22824
23560
  at: event.at,
22825
- elapsedMs: getNumber9(event.payload.elapsedMs),
22826
- error: getString14(event.payload.error),
22827
- status: getString14(event.payload.status),
22828
- toolCallId: getString14(event.payload.toolCallId),
22829
- toolName: getString14(event.payload.toolName),
23561
+ elapsedMs: getNumber10(event.payload.elapsedMs),
23562
+ error: getString15(event.payload.error),
23563
+ status: getString15(event.payload.status),
23564
+ toolCallId: getString15(event.payload.toolCallId),
23565
+ toolName: getString15(event.payload.toolName),
22830
23566
  turnId: event.turnId
22831
23567
  });
22832
23568
  var resolveOutcome4 = (events) => {
@@ -22858,6 +23594,12 @@ var buildVoiceOperationsRecord = async (options) => {
22858
23594
  });
22859
23595
  const rawAuditEvents = options.audit ? filterVoiceAuditEvents(await options.audit.list({ sessionId: options.sessionId })) : undefined;
22860
23596
  const auditEvents = options.redact && rawAuditEvents ? redactVoiceAuditEvents(rawAuditEvents, options.redact) : rawAuditEvents;
23597
+ const reviews = options.reviews ? (await options.reviews.list()).filter((review) => matchesSessionScopedId(review.id, options.sessionId)) : undefined;
23598
+ const reviewIds = new Set(reviews?.map((review) => review.id) ?? []);
23599
+ const tasks = options.tasks ? (await options.tasks.list()).filter((task) => matchesSessionScopedId(task.id, options.sessionId) || typeof task.reviewId === "string" && reviewIds.has(task.reviewId)) : undefined;
23600
+ const taskIds = new Set(tasks?.map((task) => task.id) ?? []);
23601
+ const integrationEvents = options.integrationEvents ? (await options.integrationEvents.list()).filter((event) => hasPayloadValue(event.payload, "sessionId", new Set([options.sessionId])) || hasPayloadValue(event.payload, "reviewId", reviewIds) || hasPayloadValue(event.payload, "taskId", taskIds)) : undefined;
23602
+ const sinkDeliveries = integrationEvents?.reduce((total, event) => total + Object.keys(event.sinkDeliveries ?? {}).length, 0) ?? 0;
22861
23603
  return {
22862
23604
  audit: auditEvents ? {
22863
23605
  error: countOutcome(auditEvents, "error"),
@@ -22868,12 +23610,34 @@ var buildVoiceOperationsRecord = async (options) => {
22868
23610
  } : undefined,
22869
23611
  checkedAt: Date.now(),
22870
23612
  handoffs: traceEvents.filter((event) => event.type === "agent.handoff").map(toHandoff),
23613
+ integrationEvents: integrationEvents ? {
23614
+ delivered: countIntegrationDeliveryStatus(integrationEvents, "delivered"),
23615
+ events: integrationEvents,
23616
+ failed: countIntegrationDeliveryStatus(integrationEvents, "failed"),
23617
+ pending: countIntegrationDeliveryStatus(integrationEvents, "pending"),
23618
+ sinkDeliveries,
23619
+ skipped: countIntegrationDeliveryStatus(integrationEvents, "skipped"),
23620
+ total: integrationEvents.length
23621
+ } : undefined,
22871
23622
  outcome: resolveOutcome4(traceEvents),
22872
23623
  providers: timelineSession?.providers ?? [],
22873
23624
  replay,
23625
+ reviews: reviews ? {
23626
+ failed: reviews.filter((review) => !review.summary.pass).length,
23627
+ reviews,
23628
+ total: reviews.length
23629
+ } : undefined,
22874
23630
  sessionId: options.sessionId,
22875
23631
  status: timelineSession?.status ?? "healthy",
22876
23632
  summary: timelineSession?.summary ?? replay.summary,
23633
+ tasks: tasks ? {
23634
+ done: tasks.filter((task) => task.status === "done").length,
23635
+ inProgress: tasks.filter((task) => task.status === "in-progress").length,
23636
+ open: tasks.filter((task) => task.status === "open").length,
23637
+ overdue: tasks.filter((task) => typeof task.dueAt === "number" && task.status !== "done" && task.dueAt <= Date.now()).length,
23638
+ tasks,
23639
+ total: tasks.length
23640
+ } : undefined,
22877
23641
  timeline: timelineSession?.events ?? [],
22878
23642
  tools: traceEvents.filter((event) => event.type === "agent.tool").map(toTool),
22879
23643
  traceEvents
@@ -22885,18 +23649,24 @@ var renderVoiceOperationsRecordHTML = (record, options = {}) => {
22885
23649
  const providers = record.providers.length ? record.providers.map((provider) => `<article><strong>${escapeHtml39(provider.provider)}</strong><span>${String(provider.eventCount)} events</span><span>${formatMs4(provider.averageElapsedMs)} avg</span><span>${String(provider.errorCount)} errors</span></article>`).join("") : '<p class="muted">No provider events recorded.</p>';
22886
23650
  const handoffs = record.handoffs.length ? record.handoffs.map((handoff) => `<li><strong>${escapeHtml39(handoff.fromAgentId ?? "unknown")}</strong> to <strong>${escapeHtml39(handoff.targetAgentId ?? "unknown")}</strong> <span>${escapeHtml39(handoff.status ?? "")}</span><p>${escapeHtml39(handoff.summary ?? handoff.reason ?? "")}</p></li>`).join("") : "<li>No agent handoffs recorded.</li>";
22887
23651
  const tools = record.tools.length ? record.tools.map((tool) => `<li><strong>${escapeHtml39(tool.toolName ?? "tool")}</strong> <span>${escapeHtml39(tool.status ?? "")}</span> ${formatMs4(tool.elapsedMs)} ${tool.error ? `<p>${escapeHtml39(tool.error)}</p>` : ""}</li>`).join("") : "<li>No tool calls recorded.</li>";
23652
+ const reviews = record.reviews?.reviews.length ? record.reviews.reviews.map((review) => `<li><strong>${escapeHtml39(review.title)}</strong> <span>${escapeHtml39(review.summary.outcome ?? "")}</span><p>${escapeHtml39(review.postCall?.summary ?? review.transcript.actual)}</p></li>`).join("") : "<li>No call reviews recorded.</li>";
23653
+ const tasks = record.tasks?.tasks.length ? record.tasks.tasks.map((task) => `<li><strong>${escapeHtml39(task.title)}</strong> <span>${escapeHtml39(task.status)}</span><p>${escapeHtml39(task.recommendedAction)}</p></li>`).join("") : "<li>No ops tasks recorded.</li>";
23654
+ const integrationEvents = record.integrationEvents?.events.length ? record.integrationEvents.events.map((event) => `<li><strong>${escapeHtml39(event.type)}</strong> <span>${escapeHtml39(event.deliveryStatus ?? "local")}</span><p>${escapeHtml39(event.deliveryError ?? event.deliveredTo ?? "")}</p></li>`).join("") : "<li>No integration events recorded.</li>";
22888
23655
  const snippet = escapeHtml39(`app.use(
22889
23656
  createVoiceOperationsRecordRoutes({
22890
23657
  audit: auditStore,
23658
+ integrationEvents: opsEvents,
22891
23659
  htmlPath: '/voice-ops/:sessionId',
22892
23660
  path: '/api/voice-ops/:sessionId',
22893
23661
  redact: {
22894
23662
  keys: ['authorization', 'apiKey', 'token']
22895
23663
  },
22896
- store: traceStore
23664
+ reviews: callReviews,
23665
+ store: traceStore,
23666
+ tasks: opsTasks
22897
23667
  })
22898
23668
  );`);
22899
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml39(options.title ?? "Voice Operations Record")}</title><style>body{background:#101417;color:#f9f4e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.eyebrow{color:#fbbf24;font-size:.8rem;font-weight:900;letter-spacing:.14em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.8rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:20px 0}.card,.primitive{background:#182025;border:1px solid #2d3a43;border-radius:20px;padding:16px}.card span,.muted{color:#a9b4bd}.card strong{display:block;font-size:2rem}section{margin-top:28px}article{display:grid;gap:8px}ul{display:grid;gap:10px;list-style:none;padding:0}li{background:#182025;border:1px solid #2d3a43;border-radius:16px;padding:14px}pre{background:#080d10;border:1px solid #2d3a43;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}</style></head><body><main><p class="eyebrow">Portable production proof</p><h1>${escapeHtml39(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml39(record.status)}">${escapeHtml39(record.status)}</p><section class="grid"><div class="card"><span>Events</span><strong>${String(record.summary.eventCount)}</strong></div><div class="card"><span>Turns</span><strong>${String(record.summary.turnCount)}</strong></div><div class="card"><span>Errors</span><strong>${String(record.summary.errorCount)}</strong></div><div class="card"><span>Duration</span><strong>${formatMs4(record.summary.callDurationMs)}</strong></div><div class="card"><span>Audit</span><strong>${String(record.audit?.total ?? 0)}</strong></div></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceOperationsRecordRoutes(...)</code> gives every call one debuggable object</h2><p class="muted">Use this as the support/debug payload across traces, provider routing, tools, handoffs, audit, latency, and replay.</p><pre><code>${snippet}</code></pre></section><section><h2>Providers</h2><div class="grid">${providers}</div></section><section><h2>Handoffs</h2><ul>${handoffs}</ul></section><section><h2>Tools</h2><ul>${tools}</ul></section></main></body></html>`;
23669
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml39(options.title ?? "Voice Operations Record")}</title><style>body{background:#101417;color:#f9f4e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.eyebrow{color:#fbbf24;font-size:.8rem;font-weight:900;letter-spacing:.14em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.8rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:20px 0}.card,.primitive{background:#182025;border:1px solid #2d3a43;border-radius:20px;padding:16px}.card span,.muted{color:#a9b4bd}.card strong{display:block;font-size:2rem}section{margin-top:28px}article{display:grid;gap:8px}ul{display:grid;gap:10px;list-style:none;padding:0}li{background:#182025;border:1px solid #2d3a43;border-radius:16px;padding:14px}pre{background:#080d10;border:1px solid #2d3a43;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}</style></head><body><main><p class="eyebrow">Portable production proof</p><h1>${escapeHtml39(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml39(record.status)}">${escapeHtml39(record.status)}</p><section class="grid"><div class="card"><span>Events</span><strong>${String(record.summary.eventCount)}</strong></div><div class="card"><span>Turns</span><strong>${String(record.summary.turnCount)}</strong></div><div class="card"><span>Errors</span><strong>${String(record.summary.errorCount)}</strong></div><div class="card"><span>Duration</span><strong>${formatMs4(record.summary.callDurationMs)}</strong></div><div class="card"><span>Audit</span><strong>${String(record.audit?.total ?? 0)}</strong></div><div class="card"><span>Reviews</span><strong>${String(record.reviews?.total ?? 0)}</strong></div><div class="card"><span>Tasks</span><strong>${String(record.tasks?.total ?? 0)}</strong></div><div class="card"><span>Integrations</span><strong>${String(record.integrationEvents?.total ?? 0)}</strong></div></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceOperationsRecordRoutes(...)</code> gives every call one debuggable object</h2><p class="muted">Use this as the support/debug payload across traces, provider routing, tools, handoffs, audit, latency, replay, reviews, tasks, and webhook delivery.</p><pre><code>${snippet}</code></pre></section><section><h2>Providers</h2><div class="grid">${providers}</div></section><section><h2>Handoffs</h2><ul>${handoffs}</ul></section><section><h2>Tools</h2><ul>${tools}</ul></section><section><h2>Reviews</h2><ul>${reviews}</ul></section><section><h2>Tasks</h2><ul>${tasks}</ul></section><section><h2>Integration Events</h2><ul>${integrationEvents}</ul></section></main></body></html>`;
22900
23670
  };
22901
23671
  var createVoiceOperationsRecordRoutes = (options) => {
22902
23672
  const path = options.path ?? "/api/voice-operations/:sessionId";
@@ -22908,9 +23678,12 @@ var createVoiceOperationsRecordRoutes = (options) => {
22908
23678
  audit: options.audit,
22909
23679
  evaluation: options.evaluation,
22910
23680
  events: options.events,
23681
+ integrationEvents: options.integrationEvents,
22911
23682
  redact: options.redact,
23683
+ reviews: options.reviews,
22912
23684
  sessionId,
22913
- store: options.store
23685
+ store: options.store,
23686
+ tasks: options.tasks
22914
23687
  });
22915
23688
  const getSessionId = (params) => params.sessionId ?? "";
22916
23689
  routes.get(path, async ({ params }) => Response.json(await buildRecord(getSessionId(params))));
@@ -23778,8 +24551,8 @@ var createVoiceTTSProviderRouter = (options) => {
23778
24551
  // src/traceDeliveryRoutes.ts
23779
24552
  import { Elysia as Elysia41 } from "elysia";
23780
24553
  var escapeHtml41 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
23781
- var getString15 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
23782
- var getNumber10 = (value) => {
24554
+ var getString16 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
24555
+ var getNumber11 = (value) => {
23783
24556
  if (typeof value === "number" && Number.isFinite(value)) {
23784
24557
  return value;
23785
24558
  }
@@ -23790,13 +24563,13 @@ var getNumber10 = (value) => {
23790
24563
  return;
23791
24564
  };
23792
24565
  var parseStatus2 = (value) => {
23793
- const text = getString15(value);
24566
+ const text = getString16(value);
23794
24567
  return text === "pending" || text === "delivered" || text === "failed" || text === "skipped" || text === "all" ? text : undefined;
23795
24568
  };
23796
24569
  var resolveVoiceTraceDeliveryFilter = (query = {}, base = {}) => ({
23797
24570
  ...base,
23798
- limit: getNumber10(query.limit) ?? base.limit,
23799
- q: getString15(query.q) ?? base.q,
24571
+ limit: getNumber11(query.limit) ?? base.limit,
24572
+ q: getString16(query.q) ?? base.q,
23800
24573
  status: parseStatus2(query.status) ?? base.status
23801
24574
  });
23802
24575
  var deliverySearchText2 = (delivery) => [
@@ -25535,6 +26308,7 @@ export {
25535
26308
  runVoiceProviderRoutingContract,
25536
26309
  runVoicePhoneAgentProductionSmokeContract,
25537
26310
  runVoiceOutcomeContractSuite,
26311
+ runVoiceCampaignReadinessProof,
25538
26312
  runVoiceCampaignProof,
25539
26313
  runVoiceCampaignDialerProof,
25540
26314
  runVoiceAgentSquadContract,
@@ -25584,6 +26358,7 @@ export {
25584
26358
  renderVoiceOpsActionHistoryHTML,
25585
26359
  renderVoiceOperationsRecordHTML,
25586
26360
  renderVoiceLiveLatencyHTML,
26361
+ renderVoiceLatencySLOMarkdown,
25587
26362
  renderVoiceHandoffHealthHTML,
25588
26363
  renderVoiceEvalHTML,
25589
26364
  renderVoiceEvalBaselineHTML,
@@ -25624,6 +26399,7 @@ export {
25624
26399
  listVoiceRoutingEvents,
25625
26400
  listVoiceOpsTasks,
25626
26401
  isVoiceOpsTaskOverdue,
26402
+ importVoiceCampaignRecipients,
25627
26403
  heartbeatVoiceOpsTask,
25628
26404
  hasVoiceOpsTaskSLABreach,
25629
26405
  getVoiceLiveOpsControlStatus,
@@ -25907,6 +26683,7 @@ export {
25907
26683
  buildVoiceOpsActionHistoryReport,
25908
26684
  buildVoiceOperationsRecord,
25909
26685
  buildVoiceLiveOpsControlState,
26686
+ buildVoiceLatencySLOGate,
25910
26687
  buildVoiceIncidentBundle,
25911
26688
  buildVoiceDiagnosticsMarkdown,
25912
26689
  buildVoiceDemoReadyReport,
@@ -25919,6 +26696,7 @@ export {
25919
26696
  buildVoiceAuditDeliveryReport,
25920
26697
  assignVoiceOpsTask,
25921
26698
  assertVoiceProviderRoutingContract,
26699
+ assertVoiceLatencySLOGate,
25922
26700
  assertVoiceAgentSquadContract,
25923
26701
  applyVoiceTelephonyOutcome,
25924
26702
  applyVoiceOpsTaskPolicy,