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

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)));
@@ -7374,7 +7756,7 @@ var createVoiceAgentSquad = (options) => {
7374
7756
  targetAgentId: nextAgent.id,
7375
7757
  turn: input.turn
7376
7758
  });
7377
- await appendVoiceAgentSquadHandoff({
7759
+ const handoff = await appendVoiceAgentSquadHandoff({
7378
7760
  agentId: options.id,
7379
7761
  fromAgentId: agent.id,
7380
7762
  handoffs,
@@ -7400,17 +7782,54 @@ var createVoiceAgentSquad = (options) => {
7400
7782
  sessionId: input.session.id,
7401
7783
  toAgentId: nextAgent.id
7402
7784
  });
7403
- messages.push({
7785
+ const summaryMessage = {
7404
7786
  content: handoffSummary ?? handoffReason ?? `Handoff to ${nextAgent.id}`,
7405
7787
  metadata,
7406
7788
  name: nextAgent.id,
7407
7789
  role: "system"
7790
+ };
7791
+ messages.push(summaryMessage);
7792
+ const contextPolicy = await options.contextPolicy?.({
7793
+ context: input.context,
7794
+ fromAgentId: agent.id,
7795
+ handoff,
7796
+ messages,
7797
+ session: input.session,
7798
+ summaryMessage,
7799
+ targetAgent: nextAgent,
7800
+ turn: input.turn
7801
+ });
7802
+ if (contextPolicy?.metadata && Object.keys(contextPolicy.metadata).length > 0) {
7803
+ handoff.metadata = {
7804
+ ...handoff.metadata,
7805
+ ...contextPolicy.metadata
7806
+ };
7807
+ const latest = handoffs.at(-1);
7808
+ if (latest === handoff) {
7809
+ latest.metadata = handoff.metadata;
7810
+ }
7811
+ }
7812
+ await appendVoiceAgentTrace({
7813
+ agentId: options.id,
7814
+ event: {
7815
+ fromAgentId: handoff.fromAgentId,
7816
+ messageCount: messages.length,
7817
+ nextMessageCount: contextPolicy?.messages?.length ?? messages.length,
7818
+ status: contextPolicy ? "applied" : "default",
7819
+ summaryIncluded: (contextPolicy?.messages ?? messages).some((message) => message === summaryMessage),
7820
+ targetAgentId: nextAgent.id
7821
+ },
7822
+ session: input.session,
7823
+ trace: options.trace,
7824
+ turn: input.turn,
7825
+ type: "agent.context"
7408
7826
  });
7409
7827
  agent = nextAgent;
7410
7828
  agentId = nextAgent.id;
7411
7829
  result = await agent.run({
7412
7830
  ...input,
7413
- messages
7831
+ messages: contextPolicy?.messages ?? messages,
7832
+ system: contextPolicy?.system ?? input.system
7414
7833
  });
7415
7834
  toolResults.push(...result.toolResults);
7416
7835
  }
@@ -9121,6 +9540,8 @@ var renderTraceEventMarkdown = (event, startedAt) => {
9121
9540
  return event.payload.text ? `${label} assistant "${formatTraceValue(event.payload.text)}"` : `${label} ${formatTraceValue(event.payload.status)}`;
9122
9541
  case "agent.tool":
9123
9542
  return `${label} ${formatTraceValue(event.payload.toolName)} ${formatTraceValue(event.payload.status)}`;
9543
+ case "agent.context":
9544
+ return `${label} ${formatTraceValue(event.payload.fromAgentId)} -> ${formatTraceValue(event.payload.targetAgentId)} ${formatTraceValue(event.payload.status)}`;
9124
9545
  case "agent.handoff":
9125
9546
  return `${label} ${formatTraceValue(event.payload.fromAgentId)} -> ${formatTraceValue(event.payload.targetAgentId)}`;
9126
9547
  case "session.error":
@@ -15189,6 +15610,308 @@ var createVoiceLiveLatencyRoutes = (options) => {
15189
15610
  }
15190
15611
  return routes;
15191
15612
  };
15613
+ // src/latencySlo.ts
15614
+ var DEFAULT_WARN_AFTER_MS2 = 1800;
15615
+ var DEFAULT_FAIL_AFTER_MS2 = 3200;
15616
+ var STAGE_LABELS = {
15617
+ assistant_text_to_tts_send: "Assistant text to TTS send",
15618
+ barge_in_stop: "Barge-in stop latency",
15619
+ commit_to_assistant_text: "Commit to assistant text",
15620
+ final_to_commit: "Final transcript to commit",
15621
+ live_latency: "Browser live latency",
15622
+ provider_llm: "LLM provider latency",
15623
+ provider_stt: "STT provider latency",
15624
+ provider_tts: "TTS provider latency",
15625
+ speech_to_commit: "Speech detected to commit",
15626
+ tts_send_duration: "TTS send duration",
15627
+ tts_to_first_audio: "TTS to first audio"
15628
+ };
15629
+ var TRACE_TYPES = [
15630
+ "assistant.run",
15631
+ "client.barge_in",
15632
+ "client.live_latency",
15633
+ "turn.transcript",
15634
+ "turn_latency.stage"
15635
+ ];
15636
+ var getNumber6 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
15637
+ var getString11 = (value) => typeof value === "string" && value.trim() ? value : undefined;
15638
+ var percentile2 = (values, percentileValue) => {
15639
+ if (values.length === 0) {
15640
+ return;
15641
+ }
15642
+ const sorted = [...values].sort((left, right) => left - right);
15643
+ const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(percentileValue / 100 * sorted.length) - 1));
15644
+ return Math.round(sorted[index] ?? 0);
15645
+ };
15646
+ var average = (values) => values.length === 0 ? undefined : Math.round(values.reduce((total, value) => total + value, 0) / values.length);
15647
+ var resolveBudget = (stage, options) => ({
15648
+ failAfterMs: options.budgets?.[stage]?.failAfterMs ?? options.failAfterMs ?? DEFAULT_FAIL_AFTER_MS2,
15649
+ warnAfterMs: options.budgets?.[stage]?.warnAfterMs ?? options.warnAfterMs ?? DEFAULT_WARN_AFTER_MS2
15650
+ });
15651
+ var statusForLatency = (latencyMs, budget) => latencyMs > budget.failAfterMs ? "fail" : budget.warnAfterMs !== undefined && latencyMs > budget.warnAfterMs ? "warn" : "pass";
15652
+ var stageMeasurement = (input) => {
15653
+ if (input.latencyMs === undefined) {
15654
+ return;
15655
+ }
15656
+ return {
15657
+ at: input.at,
15658
+ latencyMs: Math.max(0, Math.round(input.latencyMs)),
15659
+ label: STAGE_LABELS[input.stage],
15660
+ provider: input.provider,
15661
+ sessionId: input.sessionId,
15662
+ stage: input.stage,
15663
+ status: statusForLatency(Math.max(0, input.latencyMs), input.budget),
15664
+ turnId: input.turnId
15665
+ };
15666
+ };
15667
+ var providerStageForEvent = (event) => {
15668
+ if (event.type === "assistant.run") {
15669
+ return "provider_llm";
15670
+ }
15671
+ if (event.type === "turn.transcript") {
15672
+ return "provider_stt";
15673
+ }
15674
+ const kind = getString11(event.payload.providerKind) ?? getString11(event.payload.kind) ?? getString11(event.payload.lane);
15675
+ if (kind === "llm" || kind === "model") {
15676
+ return "provider_llm";
15677
+ }
15678
+ if (kind === "stt" || kind === "transcription") {
15679
+ return "provider_stt";
15680
+ }
15681
+ if (kind === "tts" || kind === "speech") {
15682
+ return "provider_tts";
15683
+ }
15684
+ return;
15685
+ };
15686
+ var eventElapsedMs = (event) => getNumber6(event.payload.elapsedMs) ?? getNumber6(event.payload.latencyMs) ?? getNumber6(event.payload.durationMs);
15687
+ var collectTraceStageMeasurements = (events, options) => {
15688
+ const grouped = new Map;
15689
+ for (const event of events) {
15690
+ if (event.type !== "turn_latency.stage" || !event.turnId) {
15691
+ continue;
15692
+ }
15693
+ const stage = getString11(event.payload.stage);
15694
+ if (!stage) {
15695
+ continue;
15696
+ }
15697
+ const key = `${event.sessionId}:${event.turnId}`;
15698
+ const stages = grouped.get(key) ?? new Map;
15699
+ const previous = stages.get(stage);
15700
+ if (!previous || event.at < previous.at) {
15701
+ stages.set(stage, event);
15702
+ }
15703
+ grouped.set(key, stages);
15704
+ }
15705
+ const measurements = [];
15706
+ for (const [key, stages] of grouped) {
15707
+ const [sessionId, turnId] = key.split(":");
15708
+ if (!sessionId || !turnId) {
15709
+ continue;
15710
+ }
15711
+ const speechDetected = stages.get("speech_detected")?.at;
15712
+ const finalTranscript = stages.get("final_transcript")?.at;
15713
+ const turnCommitted = stages.get("turn_committed")?.at;
15714
+ const assistantTextStarted = stages.get("assistant_text_started")?.at;
15715
+ const ttsSendStarted = stages.get("tts_send_started")?.at;
15716
+ const ttsSendCompleted = stages.get("tts_send_completed")?.at;
15717
+ const assistantAudioReceived = stages.get("assistant_audio_received")?.at;
15718
+ const candidates = [
15719
+ {
15720
+ at: turnCommitted ?? 0,
15721
+ budget: resolveBudget("speech_to_commit", options),
15722
+ latencyMs: speechDetected === undefined || turnCommitted === undefined ? undefined : turnCommitted - speechDetected,
15723
+ required: true,
15724
+ sessionId,
15725
+ stage: "speech_to_commit",
15726
+ turnId
15727
+ },
15728
+ {
15729
+ at: turnCommitted ?? 0,
15730
+ budget: resolveBudget("final_to_commit", options),
15731
+ latencyMs: finalTranscript === undefined || turnCommitted === undefined ? undefined : turnCommitted - finalTranscript,
15732
+ required: true,
15733
+ sessionId,
15734
+ stage: "final_to_commit",
15735
+ turnId
15736
+ },
15737
+ {
15738
+ at: assistantTextStarted ?? 0,
15739
+ budget: resolveBudget("commit_to_assistant_text", options),
15740
+ latencyMs: turnCommitted === undefined || assistantTextStarted === undefined ? undefined : assistantTextStarted - turnCommitted,
15741
+ required: true,
15742
+ sessionId,
15743
+ stage: "commit_to_assistant_text",
15744
+ turnId
15745
+ },
15746
+ {
15747
+ at: ttsSendStarted ?? 0,
15748
+ budget: resolveBudget("assistant_text_to_tts_send", options),
15749
+ latencyMs: assistantTextStarted === undefined || ttsSendStarted === undefined ? undefined : ttsSendStarted - assistantTextStarted,
15750
+ required: true,
15751
+ sessionId,
15752
+ stage: "assistant_text_to_tts_send",
15753
+ turnId
15754
+ },
15755
+ {
15756
+ at: ttsSendCompleted ?? 0,
15757
+ budget: resolveBudget("tts_send_duration", options),
15758
+ latencyMs: ttsSendStarted === undefined || ttsSendCompleted === undefined ? undefined : ttsSendCompleted - ttsSendStarted,
15759
+ required: true,
15760
+ sessionId,
15761
+ stage: "tts_send_duration",
15762
+ turnId
15763
+ },
15764
+ {
15765
+ at: assistantAudioReceived ?? 0,
15766
+ budget: resolveBudget("tts_to_first_audio", options),
15767
+ latencyMs: ttsSendCompleted === undefined || assistantAudioReceived === undefined ? undefined : assistantAudioReceived - ttsSendCompleted,
15768
+ required: true,
15769
+ sessionId,
15770
+ stage: "tts_to_first_audio",
15771
+ turnId
15772
+ }
15773
+ ];
15774
+ for (const candidate of candidates) {
15775
+ const measurement = stageMeasurement(candidate);
15776
+ if (measurement) {
15777
+ measurements.push(measurement);
15778
+ }
15779
+ }
15780
+ }
15781
+ return measurements;
15782
+ };
15783
+ var collectDirectMeasurements = (events, options) => {
15784
+ const measurements = [];
15785
+ for (const event of events) {
15786
+ if (event.type === "client.live_latency") {
15787
+ const stage = "live_latency";
15788
+ const measurement = stageMeasurement({
15789
+ at: event.at,
15790
+ budget: resolveBudget(stage, options),
15791
+ latencyMs: eventElapsedMs(event),
15792
+ sessionId: event.sessionId,
15793
+ stage,
15794
+ turnId: event.turnId
15795
+ });
15796
+ if (measurement) {
15797
+ measurements.push(measurement);
15798
+ }
15799
+ continue;
15800
+ }
15801
+ if (event.type === "client.barge_in") {
15802
+ const stage = "barge_in_stop";
15803
+ const measurement = stageMeasurement({
15804
+ at: event.at,
15805
+ budget: resolveBudget(stage, options),
15806
+ latencyMs: eventElapsedMs(event),
15807
+ sessionId: event.sessionId,
15808
+ stage,
15809
+ turnId: event.turnId
15810
+ });
15811
+ if (measurement) {
15812
+ measurements.push(measurement);
15813
+ }
15814
+ continue;
15815
+ }
15816
+ const providerStage = providerStageForEvent(event);
15817
+ if (providerStage) {
15818
+ const measurement = stageMeasurement({
15819
+ at: event.at,
15820
+ budget: resolveBudget(providerStage, options),
15821
+ latencyMs: eventElapsedMs(event),
15822
+ provider: getString11(event.payload.provider),
15823
+ sessionId: event.sessionId,
15824
+ stage: providerStage,
15825
+ turnId: event.turnId
15826
+ });
15827
+ if (measurement) {
15828
+ measurements.push(measurement);
15829
+ }
15830
+ }
15831
+ }
15832
+ return measurements;
15833
+ };
15834
+ var summarizeStage = (stage, measurements, options) => {
15835
+ const stageMeasurements = measurements.filter((measurement) => measurement.stage === stage);
15836
+ const latencies = stageMeasurements.map((measurement) => measurement.latencyMs);
15837
+ const failed = stageMeasurements.filter((measurement) => measurement.status === "fail").length;
15838
+ const warnings = stageMeasurements.filter((measurement) => measurement.status === "warn").length;
15839
+ return {
15840
+ averageMs: average(latencies),
15841
+ budget: resolveBudget(stage, options),
15842
+ failed,
15843
+ label: STAGE_LABELS[stage],
15844
+ maxMs: latencies.length > 0 ? Math.max(...latencies) : undefined,
15845
+ measurements: stageMeasurements,
15846
+ p50Ms: percentile2(latencies, 50),
15847
+ p95Ms: percentile2(latencies, 95),
15848
+ stage,
15849
+ status: stageMeasurements.length === 0 ? "empty" : failed > 0 ? "fail" : warnings > 0 ? "warn" : "pass",
15850
+ total: stageMeasurements.length,
15851
+ warnings
15852
+ };
15853
+ };
15854
+ var buildVoiceLatencySLOGate = async (options) => {
15855
+ const events = options.events ?? await options.store?.list({
15856
+ limit: options.limit ?? 1000,
15857
+ type: TRACE_TYPES
15858
+ }) ?? [];
15859
+ const measurements = [
15860
+ ...collectTraceStageMeasurements(events, options),
15861
+ ...collectDirectMeasurements(events, options)
15862
+ ].sort((left, right) => right.at - left.at);
15863
+ const stageKeys = new Set([
15864
+ ...Object.keys(options.budgets ?? {}),
15865
+ ...measurements.map((measurement) => measurement.stage)
15866
+ ]);
15867
+ const stages = [...stageKeys].map((stage) => summarizeStage(stage, measurements, options)).sort((left, right) => left.label.localeCompare(right.label));
15868
+ const failed = measurements.filter((measurement) => measurement.status === "fail").length;
15869
+ const warnings = measurements.filter((measurement) => measurement.status === "warn").length;
15870
+ return {
15871
+ checkedAt: Date.now(),
15872
+ failed,
15873
+ measurements,
15874
+ stages,
15875
+ status: measurements.length === 0 ? "empty" : failed > 0 ? "fail" : warnings > 0 ? "warn" : "pass",
15876
+ total: measurements.length,
15877
+ warnings
15878
+ };
15879
+ };
15880
+ var assertVoiceLatencySLOGate = async (options) => {
15881
+ const report = await buildVoiceLatencySLOGate(options);
15882
+ if (report.status === "fail") {
15883
+ const error = new Error(`Voice latency SLO gate failed with ${report.failed} failed measurement(s).`);
15884
+ error.report = report;
15885
+ throw error;
15886
+ }
15887
+ return report;
15888
+ };
15889
+ var renderVoiceLatencySLOMarkdown = (report, options = {}) => {
15890
+ const title = options.title ?? "Voice Latency SLO Gate";
15891
+ 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(`
15892
+ `);
15893
+ 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(`
15894
+ `);
15895
+ return `# ${title}
15896
+
15897
+ Status: ${report.status}
15898
+
15899
+ Total measurements: ${report.total}
15900
+ Warnings: ${report.warnings}
15901
+ Failures: ${report.failed}
15902
+
15903
+ | Stage | Status | Samples | p50 ms | p95 ms | Warn ms | Fail ms |
15904
+ | --- | --- | ---: | ---: | ---: | ---: | ---: |
15905
+ ${rows || "| No latency measurements | empty | 0 | n/a | n/a | n/a | n/a |"}
15906
+
15907
+ ${failures ? `## Failures
15908
+
15909
+ ${failures}
15910
+ ` : `## Failures
15911
+
15912
+ None.
15913
+ `}`;
15914
+ };
15192
15915
  // src/turnQuality.ts
15193
15916
  import { Elysia as Elysia24 } from "elysia";
15194
15917
  var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
@@ -20237,8 +20960,8 @@ var createVoiceProviderCapabilityRoutes = (options) => {
20237
20960
  // src/resilienceRoutes.ts
20238
20961
  import { Elysia as Elysia33 } from "elysia";
20239
20962
  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;
20963
+ var getString12 = (value) => typeof value === "string" ? value : undefined;
20964
+ var getNumber7 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
20242
20965
  var getBoolean2 = (value) => value === true;
20243
20966
  var isProviderStatus2 = (value) => value === "error" || value === "fallback" || value === "success";
20244
20967
  var listVoiceRoutingEvents = (events) => {
@@ -20247,27 +20970,27 @@ var listVoiceRoutingEvents = (events) => {
20247
20970
  if (event.type !== "session.error") {
20248
20971
  continue;
20249
20972
  }
20250
- const provider = getString11(event.payload.provider);
20973
+ const provider = getString12(event.payload.provider);
20251
20974
  const providerStatus = isProviderStatus2(event.payload.providerStatus) ? event.payload.providerStatus : undefined;
20252
20975
  if (!provider || !providerStatus) {
20253
20976
  continue;
20254
20977
  }
20255
- const kind = getString11(event.payload.kind);
20978
+ const kind = getString12(event.payload.kind);
20256
20979
  routingEvents.push({
20257
20980
  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),
20981
+ attempt: getNumber7(event.payload.attempt),
20982
+ elapsedMs: getNumber7(event.payload.elapsedMs),
20983
+ error: getString12(event.payload.error),
20984
+ fallbackProvider: getString12(event.payload.fallbackProvider),
20262
20985
  kind: kind === "stt" || kind === "tts" ? kind : "llm",
20263
- latencyBudgetMs: getNumber6(event.payload.latencyBudgetMs),
20264
- operation: getString11(event.payload.operation),
20986
+ latencyBudgetMs: getNumber7(event.payload.latencyBudgetMs),
20987
+ operation: getString12(event.payload.operation),
20265
20988
  provider,
20266
- routing: getString11(event.payload.routing),
20267
- selectedProvider: getString11(event.payload.selectedProvider),
20989
+ routing: getString12(event.payload.routing),
20990
+ selectedProvider: getString12(event.payload.selectedProvider),
20268
20991
  sessionId: event.sessionId,
20269
20992
  status: providerStatus,
20270
- suppressionRemainingMs: getNumber6(event.payload.suppressionRemainingMs),
20993
+ suppressionRemainingMs: getNumber7(event.payload.suppressionRemainingMs),
20271
20994
  timedOut: getBoolean2(event.payload.timedOut),
20272
20995
  turnId: event.turnId
20273
20996
  });
@@ -20764,6 +21487,7 @@ var readinessGateCodes = {
20764
21487
  "Audit evidence": "voice.readiness.audit_evidence",
20765
21488
  "Audit sink delivery": "voice.readiness.audit_sink_delivery",
20766
21489
  "Barge-in interruption proof": "voice.readiness.barge_in_interruption",
21490
+ "Campaign readiness proof": "voice.readiness.campaign_readiness",
20767
21491
  "Carrier readiness": "voice.readiness.carrier_readiness",
20768
21492
  "Delivery runtime": "voice.readiness.delivery_runtime",
20769
21493
  "Handoff delivery": "voice.readiness.handoff_delivery",
@@ -20877,6 +21601,12 @@ var resolveBargeInReports = async (options, input) => {
20877
21601
  }
20878
21602
  return typeof options.bargeInReports === "function" ? await options.bargeInReports(input) : options.bargeInReports;
20879
21603
  };
21604
+ var resolveCampaignReadiness = async (options, input) => {
21605
+ if (options.campaignReadiness === false || options.campaignReadiness === undefined) {
21606
+ return;
21607
+ }
21608
+ return typeof options.campaignReadiness === "function" ? await options.campaignReadiness(input) : options.campaignReadiness;
21609
+ };
20880
21610
  var resolveProofSources = async (options, input) => {
20881
21611
  if (options.proofSources === false || options.proofSources === undefined) {
20882
21612
  return;
@@ -21097,8 +21827,8 @@ var summarizeLiveLatency = (events, options) => {
21097
21827
  warnings
21098
21828
  };
21099
21829
  };
21100
- var getString12 = (value) => typeof value === "string" ? value : undefined;
21101
- var getNumber7 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
21830
+ var getString13 = (value) => typeof value === "string" ? value : undefined;
21831
+ var getNumber8 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
21102
21832
  var voiceOperationsRecordHref = (base, sessionId) => {
21103
21833
  const encoded = encodeURIComponent(sessionId);
21104
21834
  if (base.includes(":sessionId")) {
@@ -21109,7 +21839,7 @@ var voiceOperationsRecordHref = (base, sessionId) => {
21109
21839
  var buildOperationsRecordLinks = (input) => {
21110
21840
  const failedSessionSet = new Set(input.failedSessionIds);
21111
21841
  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),
21842
+ detail: getString13(event.payload.error),
21113
21843
  href: voiceOperationsRecordHref(input.base, event.sessionId),
21114
21844
  label: "Open provider error operations record",
21115
21845
  sessionId: event.sessionId,
@@ -21117,7 +21847,7 @@ var buildOperationsRecordLinks = (input) => {
21117
21847
  }));
21118
21848
  const failingLatency = input.events.filter((event) => event.type === "client.live_latency").map((event) => ({
21119
21849
  event,
21120
- latencyMs: getNumber7(event.payload.latencyMs) ?? getNumber7(event.payload.elapsedMs)
21850
+ latencyMs: getNumber8(event.payload.latencyMs) ?? getNumber8(event.payload.elapsedMs)
21121
21851
  })).filter((entry) => entry.latencyMs !== undefined && entry.latencyMs > input.liveLatencyWarnAfterMs).map(({ event, latencyMs }) => ({
21122
21852
  detail: `${latencyMs}ms live latency`,
21123
21853
  href: voiceOperationsRecordHref(input.base, event.sessionId),
@@ -21163,6 +21893,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21163
21893
  phoneAgentSmokes,
21164
21894
  reconnectContracts,
21165
21895
  bargeInReports,
21896
+ campaignReadiness,
21166
21897
  proofSources
21167
21898
  ] = await Promise.all([
21168
21899
  evaluateVoiceQuality({ events }),
@@ -21195,6 +21926,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21195
21926
  resolvePhoneAgentSmokes(options, { query, request }),
21196
21927
  resolveReconnectContracts(options, { query, request }),
21197
21928
  resolveBargeInReports(options, { query, request }),
21929
+ resolveCampaignReadiness(options, { query, request }),
21198
21930
  resolveProofSources(options, { query, request })
21199
21931
  ]);
21200
21932
  const deliveryRuntime = summarizeDeliveryRuntime(deliveryRuntimeSummary);
@@ -21381,6 +22113,12 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21381
22113
  total: bargeInReports.reduce((total, report) => total + report.total, 0),
21382
22114
  warnings: bargeInReports.filter((report) => report.status === "warn").length
21383
22115
  } : undefined;
22116
+ const campaignReadinessSummary = campaignReadiness ? {
22117
+ failed: campaignReadiness.checks.filter((check) => check.status !== "pass").length,
22118
+ passed: campaignReadiness.checks.filter((check) => check.status === "pass").length,
22119
+ status: campaignReadiness.ok ? "pass" : "fail",
22120
+ total: campaignReadiness.checks.length
22121
+ } : undefined;
21384
22122
  if (agentSquadContractSummary) {
21385
22123
  checks.push({
21386
22124
  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 +22240,24 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21502
22240
  ]
21503
22241
  });
21504
22242
  }
22243
+ if (campaignReadinessSummary) {
22244
+ const failedChecks = campaignReadiness?.checks.filter((check) => check.status !== "pass").map((check) => check.name);
22245
+ checks.push({
22246
+ 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.",
22247
+ href: options.links?.campaignReadiness ?? "/api/voice/campaigns/readiness-proof",
22248
+ label: "Campaign readiness proof",
22249
+ proofSource: proofSource("campaignReadiness", "campaigns"),
22250
+ status: campaignReadinessSummary.status,
22251
+ value: `${campaignReadinessSummary.passed}/${campaignReadinessSummary.total}`,
22252
+ actions: campaignReadinessSummary.status === "pass" ? [] : [
22253
+ {
22254
+ description: "Open campaign readiness proof and inspect import, scheduling, rate-limit, and retry checks.",
22255
+ href: options.links?.campaignReadiness ?? "/api/voice/campaigns/readiness-proof",
22256
+ label: "Open campaign proof"
22257
+ }
22258
+ ]
22259
+ });
22260
+ }
21505
22261
  if (audit) {
21506
22262
  const missingLabels = audit.missing.map((requirement) => requirement.label ?? requirement.type);
21507
22263
  checks.push({
@@ -21611,6 +22367,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21611
22367
  audit: "/audit",
21612
22368
  auditDeliveries: "/audit",
21613
22369
  bargeIn: "/barge-in",
22370
+ campaignReadiness: "/api/voice/campaigns/readiness-proof",
21614
22371
  carriers: "/carriers",
21615
22372
  deliveryRuntime: "/delivery-runtime",
21616
22373
  handoffs: "/handoffs",
@@ -21637,6 +22394,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21637
22394
  audit,
21638
22395
  auditDeliveries,
21639
22396
  bargeIn: bargeInSummary,
22397
+ campaignReadiness: campaignReadinessSummary,
21640
22398
  carriers: carrierSummary,
21641
22399
  deliveryRuntime,
21642
22400
  handoffs: {
@@ -21762,6 +22520,7 @@ var profileSurfaceLabels = {
21762
22520
  },
21763
22521
  "phone-agent": {
21764
22522
  auditDeliveries: "audit delivery queue configured",
22523
+ campaignReadiness: "campaign readiness proof configured",
21765
22524
  carriers: "carrier readiness configured",
21766
22525
  deliveryRuntime: "delivery runtime configured",
21767
22526
  phoneAgentSmokes: "phone-agent smoke proof configured",
@@ -21785,6 +22544,7 @@ var profileRequiredKeys = {
21785
22544
  "phone-agent": [
21786
22545
  "carriers",
21787
22546
  "phoneAgentSmokes",
22547
+ "campaignReadiness",
21788
22548
  "providerRoutingContracts",
21789
22549
  "auditDeliveries",
21790
22550
  "traceDeliveries",
@@ -21797,6 +22557,7 @@ var configuredProfileKeys = (options) => {
21797
22557
  "audit",
21798
22558
  "auditDeliveries",
21799
22559
  "bargeInReports",
22560
+ "campaignReadiness",
21800
22561
  "carriers",
21801
22562
  "deliveryRuntime",
21802
22563
  "opsActionHistory",
@@ -21893,6 +22654,12 @@ var profileExplanation = (profile, options, links) => {
21893
22654
  key: "phoneAgentSmokes",
21894
22655
  label: "Phone agent smoke"
21895
22656
  },
22657
+ {
22658
+ configured: isConfigured(options.campaignReadiness),
22659
+ href: links.campaignReadiness,
22660
+ key: "campaignReadiness",
22661
+ label: "Campaign readiness proof"
22662
+ },
21896
22663
  {
21897
22664
  configured: true,
21898
22665
  href: links.handoffs,
@@ -21988,6 +22755,7 @@ var createVoiceReadinessProfile = (profile, options = {}) => {
21988
22755
  if (profile === "phone-agent") {
21989
22756
  const links2 = mergeLinks({
21990
22757
  auditDeliveries: "/audit/deliveries",
22758
+ campaignReadiness: "/api/voice/campaigns/readiness-proof",
21991
22759
  carriers: "/carriers",
21992
22760
  deliveryRuntime: "/delivery-runtime",
21993
22761
  handoffs: "/handoffs",
@@ -21999,6 +22767,7 @@ var createVoiceReadinessProfile = (profile, options = {}) => {
21999
22767
  }, options.links);
22000
22768
  return withDefined({
22001
22769
  auditDeliveries: options.auditDeliveries,
22770
+ campaignReadiness: options.campaignReadiness,
22002
22771
  carriers: options.carriers,
22003
22772
  deliveryRuntime: options.deliveryRuntime,
22004
22773
  gate: options.gate,
@@ -22551,11 +23320,11 @@ import { Elysia as Elysia38 } from "elysia";
22551
23320
  // src/traceTimeline.ts
22552
23321
  import { Elysia as Elysia37 } from "elysia";
22553
23322
  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;
23323
+ var getString14 = (value) => typeof value === "string" && value.trim() ? value : undefined;
23324
+ var getNumber9 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
22556
23325
  var firstString3 = (payload, keys) => {
22557
23326
  for (const key of keys) {
22558
- const value = getString13(payload[key]);
23327
+ const value = getString14(payload[key]);
22559
23328
  if (value) {
22560
23329
  return value;
22561
23330
  }
@@ -22564,7 +23333,7 @@ var firstString3 = (payload, keys) => {
22564
23333
  };
22565
23334
  var firstNumber3 = (payload, keys) => {
22566
23335
  for (const key of keys) {
22567
- const value = getNumber8(payload[key]);
23336
+ const value = getNumber9(payload[key]);
22568
23337
  if (value !== undefined) {
22569
23338
  return value;
22570
23339
  }
@@ -22584,7 +23353,7 @@ var eventStatus = (event) => firstString3(event.payload, [
22584
23353
  "type",
22585
23354
  "reason"
22586
23355
  ]);
22587
- var eventElapsedMs = (event) => firstNumber3(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
23356
+ var eventElapsedMs2 = (event) => firstNumber3(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
22588
23357
  var timelineLabel = (event) => {
22589
23358
  switch (event.type) {
22590
23359
  case "call.lifecycle":
@@ -22592,15 +23361,15 @@ var timelineLabel = (event) => {
22592
23361
  case "turn.transcript":
22593
23362
  return event.payload.isFinal === true ? "Final transcript" : "Partial transcript";
22594
23363
  case "turn.committed":
22595
- return `Committed turn${getString13(event.payload.reason) ? ` (${getString13(event.payload.reason)})` : ""}`;
23364
+ return `Committed turn${getString14(event.payload.reason) ? ` (${getString14(event.payload.reason)})` : ""}`;
22596
23365
  case "turn.assistant":
22597
23366
  return "Assistant reply";
22598
23367
  case "agent.model":
22599
23368
  return `Model call${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
22600
23369
  case "agent.tool":
22601
- return `Tool ${getString13(event.payload.toolName) ?? "call"}`;
23370
+ return `Tool ${getString14(event.payload.toolName) ?? "call"}`;
22602
23371
  case "agent.handoff":
22603
- return `Agent handoff${getString13(event.payload.targetAgentId) ? ` to ${getString13(event.payload.targetAgentId)}` : ""}`;
23372
+ return `Agent handoff${getString14(event.payload.targetAgentId) ? ` to ${getString14(event.payload.targetAgentId)}` : ""}`;
22604
23373
  case "assistant.run":
22605
23374
  return `Assistant run${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
22606
23375
  case "assistant.guardrail":
@@ -22608,13 +23377,13 @@ var timelineLabel = (event) => {
22608
23377
  case "call.handoff":
22609
23378
  return `Call handoff ${eventStatus(event) ?? ""}`.trim();
22610
23379
  case "client.live_latency":
22611
- return `Live latency${eventElapsedMs(event) !== undefined ? ` ${eventElapsedMs(event)}ms` : ""}`;
23380
+ return `Live latency${eventElapsedMs2(event) !== undefined ? ` ${eventElapsedMs2(event)}ms` : ""}`;
22612
23381
  case "session.error":
22613
- return `Error${getString13(event.payload.error) ? `: ${getString13(event.payload.error)}` : ""}`;
23382
+ return `Error${getString14(event.payload.error) ? `: ${getString14(event.payload.error)}` : ""}`;
22614
23383
  case "turn.cost":
22615
23384
  return "Cost telemetry";
22616
23385
  case "turn_latency.stage":
22617
- return `Latency ${getString13(event.payload.stage) ?? "stage"}`;
23386
+ return `Latency ${getString14(event.payload.stage) ?? "stage"}`;
22618
23387
  case "workflow.contract":
22619
23388
  return `Workflow contract ${eventStatus(event) ?? ""}`.trim();
22620
23389
  default:
@@ -22646,7 +23415,7 @@ var summarizeProviders = (events) => {
22646
23415
  }
22647
23416
  const entry = getEntry(provider);
22648
23417
  const status = eventStatus(event);
22649
- const elapsedMs = eventElapsedMs(event);
23418
+ const elapsedMs = eventElapsedMs2(event);
22650
23419
  entry.eventCount += 1;
22651
23420
  if (elapsedMs !== undefined) {
22652
23421
  entry.elapsed.push(elapsedMs);
@@ -22692,7 +23461,7 @@ var summarizeVoiceTraceTimeline = (events, options = {}) => {
22692
23461
  evaluation,
22693
23462
  events: sorted.map((event) => ({
22694
23463
  at: event.at,
22695
- elapsedMs: eventElapsedMs(event),
23464
+ elapsedMs: eventElapsedMs2(event),
22696
23465
  id: event.id,
22697
23466
  label: timelineLabel(event),
22698
23467
  offsetMs: Math.max(0, event.at - startedAt),
@@ -22807,26 +23576,32 @@ var createVoiceTraceTimelineRoutes = (options) => {
22807
23576
  };
22808
23577
 
22809
23578
  // src/operationsRecord.ts
22810
- var getString14 = (value) => typeof value === "string" ? value : undefined;
22811
- var getNumber9 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
23579
+ var getString15 = (value) => typeof value === "string" ? value : undefined;
23580
+ var getNumber10 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
22812
23581
  var countOutcome = (events, outcome) => events.filter((event) => event.outcome === outcome).length;
23582
+ var matchesSessionScopedId = (id, sessionId) => id === sessionId || id.startsWith(`${sessionId}:`);
23583
+ var hasPayloadValue = (payload, key, values) => {
23584
+ const value = payload[key];
23585
+ return typeof value === "string" && values.has(value);
23586
+ };
23587
+ var countIntegrationDeliveryStatus = (events, status) => events.filter((event) => event.deliveryStatus === status).length;
22813
23588
  var toHandoff = (event) => ({
22814
23589
  at: event.at,
22815
- fromAgentId: getString14(event.payload.fromAgentId),
23590
+ fromAgentId: getString15(event.payload.fromAgentId),
22816
23591
  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),
23592
+ reason: getString15(event.payload.reason),
23593
+ status: getString15(event.payload.status),
23594
+ summary: getString15(event.payload.summary),
23595
+ targetAgentId: getString15(event.payload.targetAgentId),
22821
23596
  turnId: event.turnId
22822
23597
  });
22823
23598
  var toTool = (event) => ({
22824
23599
  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),
23600
+ elapsedMs: getNumber10(event.payload.elapsedMs),
23601
+ error: getString15(event.payload.error),
23602
+ status: getString15(event.payload.status),
23603
+ toolCallId: getString15(event.payload.toolCallId),
23604
+ toolName: getString15(event.payload.toolName),
22830
23605
  turnId: event.turnId
22831
23606
  });
22832
23607
  var resolveOutcome4 = (events) => {
@@ -22858,6 +23633,12 @@ var buildVoiceOperationsRecord = async (options) => {
22858
23633
  });
22859
23634
  const rawAuditEvents = options.audit ? filterVoiceAuditEvents(await options.audit.list({ sessionId: options.sessionId })) : undefined;
22860
23635
  const auditEvents = options.redact && rawAuditEvents ? redactVoiceAuditEvents(rawAuditEvents, options.redact) : rawAuditEvents;
23636
+ const reviews = options.reviews ? (await options.reviews.list()).filter((review) => matchesSessionScopedId(review.id, options.sessionId)) : undefined;
23637
+ const reviewIds = new Set(reviews?.map((review) => review.id) ?? []);
23638
+ const tasks = options.tasks ? (await options.tasks.list()).filter((task) => matchesSessionScopedId(task.id, options.sessionId) || typeof task.reviewId === "string" && reviewIds.has(task.reviewId)) : undefined;
23639
+ const taskIds = new Set(tasks?.map((task) => task.id) ?? []);
23640
+ 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;
23641
+ const sinkDeliveries = integrationEvents?.reduce((total, event) => total + Object.keys(event.sinkDeliveries ?? {}).length, 0) ?? 0;
22861
23642
  return {
22862
23643
  audit: auditEvents ? {
22863
23644
  error: countOutcome(auditEvents, "error"),
@@ -22868,12 +23649,34 @@ var buildVoiceOperationsRecord = async (options) => {
22868
23649
  } : undefined,
22869
23650
  checkedAt: Date.now(),
22870
23651
  handoffs: traceEvents.filter((event) => event.type === "agent.handoff").map(toHandoff),
23652
+ integrationEvents: integrationEvents ? {
23653
+ delivered: countIntegrationDeliveryStatus(integrationEvents, "delivered"),
23654
+ events: integrationEvents,
23655
+ failed: countIntegrationDeliveryStatus(integrationEvents, "failed"),
23656
+ pending: countIntegrationDeliveryStatus(integrationEvents, "pending"),
23657
+ sinkDeliveries,
23658
+ skipped: countIntegrationDeliveryStatus(integrationEvents, "skipped"),
23659
+ total: integrationEvents.length
23660
+ } : undefined,
22871
23661
  outcome: resolveOutcome4(traceEvents),
22872
23662
  providers: timelineSession?.providers ?? [],
22873
23663
  replay,
23664
+ reviews: reviews ? {
23665
+ failed: reviews.filter((review) => !review.summary.pass).length,
23666
+ reviews,
23667
+ total: reviews.length
23668
+ } : undefined,
22874
23669
  sessionId: options.sessionId,
22875
23670
  status: timelineSession?.status ?? "healthy",
22876
23671
  summary: timelineSession?.summary ?? replay.summary,
23672
+ tasks: tasks ? {
23673
+ done: tasks.filter((task) => task.status === "done").length,
23674
+ inProgress: tasks.filter((task) => task.status === "in-progress").length,
23675
+ open: tasks.filter((task) => task.status === "open").length,
23676
+ overdue: tasks.filter((task) => typeof task.dueAt === "number" && task.status !== "done" && task.dueAt <= Date.now()).length,
23677
+ tasks,
23678
+ total: tasks.length
23679
+ } : undefined,
22877
23680
  timeline: timelineSession?.events ?? [],
22878
23681
  tools: traceEvents.filter((event) => event.type === "agent.tool").map(toTool),
22879
23682
  traceEvents
@@ -22885,18 +23688,24 @@ var renderVoiceOperationsRecordHTML = (record, options = {}) => {
22885
23688
  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
23689
  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
23690
  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>";
23691
+ 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>";
23692
+ 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>";
23693
+ 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
23694
  const snippet = escapeHtml39(`app.use(
22889
23695
  createVoiceOperationsRecordRoutes({
22890
23696
  audit: auditStore,
23697
+ integrationEvents: opsEvents,
22891
23698
  htmlPath: '/voice-ops/:sessionId',
22892
23699
  path: '/api/voice-ops/:sessionId',
22893
23700
  redact: {
22894
23701
  keys: ['authorization', 'apiKey', 'token']
22895
23702
  },
22896
- store: traceStore
23703
+ reviews: callReviews,
23704
+ store: traceStore,
23705
+ tasks: opsTasks
22897
23706
  })
22898
23707
  );`);
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>`;
23708
+ 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
23709
  };
22901
23710
  var createVoiceOperationsRecordRoutes = (options) => {
22902
23711
  const path = options.path ?? "/api/voice-operations/:sessionId";
@@ -22908,9 +23717,12 @@ var createVoiceOperationsRecordRoutes = (options) => {
22908
23717
  audit: options.audit,
22909
23718
  evaluation: options.evaluation,
22910
23719
  events: options.events,
23720
+ integrationEvents: options.integrationEvents,
22911
23721
  redact: options.redact,
23722
+ reviews: options.reviews,
22912
23723
  sessionId,
22913
- store: options.store
23724
+ store: options.store,
23725
+ tasks: options.tasks
22914
23726
  });
22915
23727
  const getSessionId = (params) => params.sessionId ?? "";
22916
23728
  routes.get(path, async ({ params }) => Response.json(await buildRecord(getSessionId(params))));
@@ -23778,8 +24590,8 @@ var createVoiceTTSProviderRouter = (options) => {
23778
24590
  // src/traceDeliveryRoutes.ts
23779
24591
  import { Elysia as Elysia41 } from "elysia";
23780
24592
  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) => {
24593
+ var getString16 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
24594
+ var getNumber11 = (value) => {
23783
24595
  if (typeof value === "number" && Number.isFinite(value)) {
23784
24596
  return value;
23785
24597
  }
@@ -23790,13 +24602,13 @@ var getNumber10 = (value) => {
23790
24602
  return;
23791
24603
  };
23792
24604
  var parseStatus2 = (value) => {
23793
- const text = getString15(value);
24605
+ const text = getString16(value);
23794
24606
  return text === "pending" || text === "delivered" || text === "failed" || text === "skipped" || text === "all" ? text : undefined;
23795
24607
  };
23796
24608
  var resolveVoiceTraceDeliveryFilter = (query = {}, base = {}) => ({
23797
24609
  ...base,
23798
- limit: getNumber10(query.limit) ?? base.limit,
23799
- q: getString15(query.q) ?? base.q,
24610
+ limit: getNumber11(query.limit) ?? base.limit,
24611
+ q: getString16(query.q) ?? base.q,
23800
24612
  status: parseStatus2(query.status) ?? base.status
23801
24613
  });
23802
24614
  var deliverySearchText2 = (delivery) => [
@@ -25535,6 +26347,7 @@ export {
25535
26347
  runVoiceProviderRoutingContract,
25536
26348
  runVoicePhoneAgentProductionSmokeContract,
25537
26349
  runVoiceOutcomeContractSuite,
26350
+ runVoiceCampaignReadinessProof,
25538
26351
  runVoiceCampaignProof,
25539
26352
  runVoiceCampaignDialerProof,
25540
26353
  runVoiceAgentSquadContract,
@@ -25584,6 +26397,7 @@ export {
25584
26397
  renderVoiceOpsActionHistoryHTML,
25585
26398
  renderVoiceOperationsRecordHTML,
25586
26399
  renderVoiceLiveLatencyHTML,
26400
+ renderVoiceLatencySLOMarkdown,
25587
26401
  renderVoiceHandoffHealthHTML,
25588
26402
  renderVoiceEvalHTML,
25589
26403
  renderVoiceEvalBaselineHTML,
@@ -25624,6 +26438,7 @@ export {
25624
26438
  listVoiceRoutingEvents,
25625
26439
  listVoiceOpsTasks,
25626
26440
  isVoiceOpsTaskOverdue,
26441
+ importVoiceCampaignRecipients,
25627
26442
  heartbeatVoiceOpsTask,
25628
26443
  hasVoiceOpsTaskSLABreach,
25629
26444
  getVoiceLiveOpsControlStatus,
@@ -25907,6 +26722,7 @@ export {
25907
26722
  buildVoiceOpsActionHistoryReport,
25908
26723
  buildVoiceOperationsRecord,
25909
26724
  buildVoiceLiveOpsControlState,
26725
+ buildVoiceLatencySLOGate,
25910
26726
  buildVoiceIncidentBundle,
25911
26727
  buildVoiceDiagnosticsMarkdown,
25912
26728
  buildVoiceDemoReadyReport,
@@ -25919,6 +26735,7 @@ export {
25919
26735
  buildVoiceAuditDeliveryReport,
25920
26736
  assignVoiceOpsTask,
25921
26737
  assertVoiceProviderRoutingContract,
26738
+ assertVoiceLatencySLOGate,
25922
26739
  assertVoiceAgentSquadContract,
25923
26740
  applyVoiceTelephonyOutcome,
25924
26741
  applyVoiceOpsTaskPolicy,