@absolutejs/voice 0.0.22-beta.191 → 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
@@ -4649,9 +4649,32 @@ var createVoiceSession = (options) => {
4649
4649
  });
4650
4650
  };
4651
4651
  const completeTurn = async (session, turn) => {
4652
+ const liveOpsControl = await options.liveOps?.getControl(options.id);
4653
+ if (liveOpsControl?.assistantPaused || liveOpsControl?.operatorTakeover) {
4654
+ await appendTrace({
4655
+ metadata: {
4656
+ source: "voice-live-ops"
4657
+ },
4658
+ payload: {
4659
+ action: "turn.skipped",
4660
+ control: liveOpsControl,
4661
+ reason: liveOpsControl.operatorTakeover ? "operator-takeover" : "assistant-paused",
4662
+ status: "skipped"
4663
+ },
4664
+ session,
4665
+ turnId: turn.id,
4666
+ type: "operator.action"
4667
+ });
4668
+ return;
4669
+ }
4670
+ const injectedInstruction = liveOpsControl?.injectedInstruction?.trim();
4652
4671
  const committedOutput = await options.route.onTurn({
4653
4672
  api,
4654
4673
  context: options.context,
4674
+ liveOps: liveOpsControl ? {
4675
+ control: liveOpsControl,
4676
+ injectedInstruction
4677
+ } : undefined,
4655
4678
  session,
4656
4679
  turn
4657
4680
  });
@@ -5407,6 +5430,7 @@ var voice = (config) => {
5407
5430
  handoff: config.handoff,
5408
5431
  languageStrategy: config.languageStrategy,
5409
5432
  lexicon,
5433
+ liveOps: config.liveOps,
5410
5434
  logger: sessionOptions.logger,
5411
5435
  phraseHints,
5412
5436
  reconnect: sessionOptions.reconnect,
@@ -5628,6 +5652,192 @@ var maybeCompleteCampaign = (record) => {
5628
5652
  record.campaign.status = "completed";
5629
5653
  }
5630
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
+ };
5631
5841
  var createVoiceMemoryCampaignStore = () => {
5632
5842
  const campaigns = new Map;
5633
5843
  return {
@@ -5788,24 +5998,28 @@ var buildVoiceCampaignObservabilityReport = async (records, options = {}) => {
5788
5998
  report.stuck.recipients.sort((left, right) => left.updatedAt - right.updatedAt);
5789
5999
  return report;
5790
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
+ };
5791
6016
  var createVoiceCampaign = (options) => {
5792
6017
  const { store } = options;
6018
+ const now = options.now ?? Date.now;
5793
6019
  return {
5794
6020
  addRecipients: async (campaignId, recipients) => {
5795
6021
  const record = await ensureRecord(store, campaignId);
5796
- const at = Date.now();
5797
- record.recipients.push(...recipients.map((recipient) => ({
5798
- attempts: 0,
5799
- createdAt: at,
5800
- id: recipient.id ?? createId2(),
5801
- metadata: recipient.metadata,
5802
- name: recipient.name,
5803
- phone: recipient.phone,
5804
- status: "pending",
5805
- updatedAt: at,
5806
- variables: recipient.variables
5807
- })));
5808
- return saveRecord(store, record);
6022
+ return runtimeAddRecipients(store, record, recipients);
5809
6023
  },
5810
6024
  cancel: async (campaignId) => {
5811
6025
  const record = await ensureRecord(store, campaignId);
@@ -5850,7 +6064,7 @@ var createVoiceCampaign = (options) => {
5850
6064
  return saveRecord(store, record);
5851
6065
  },
5852
6066
  create: async (input) => {
5853
- const at = Date.now();
6067
+ const at = now();
5854
6068
  const campaign = {
5855
6069
  createdAt: at,
5856
6070
  description: input.description,
@@ -5859,6 +6073,7 @@ var createVoiceCampaign = (options) => {
5859
6073
  maxConcurrentAttempts: input.maxConcurrentAttempts ?? 1,
5860
6074
  metadata: input.metadata,
5861
6075
  name: input.name,
6076
+ schedule: input.schedule,
5862
6077
  status: "draft",
5863
6078
  updatedAt: at
5864
6079
  };
@@ -5879,6 +6094,15 @@ var createVoiceCampaign = (options) => {
5879
6094
  return saveRecord(store, record);
5880
6095
  },
5881
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
+ },
5882
6106
  list: async () => await store.list(),
5883
6107
  pause: async (campaignId) => {
5884
6108
  const record = await ensureRecord(store, campaignId);
@@ -5898,6 +6122,7 @@ var createVoiceCampaign = (options) => {
5898
6122
  const record = await ensureRecord(store, campaignId);
5899
6123
  const result = {
5900
6124
  attempted: 0,
6125
+ blocked: [],
5901
6126
  campaignId,
5902
6127
  errors: [],
5903
6128
  started: []
@@ -5905,9 +6130,53 @@ var createVoiceCampaign = (options) => {
5905
6130
  if (record.campaign.status !== "running") {
5906
6131
  return result;
5907
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
+ }
5908
6147
  const capacity = Math.max(0, record.campaign.maxConcurrentAttempts - activeAttemptCount(record));
5909
- const candidates = record.recipients.filter((recipient) => recipient.status === "queued" && recipient.attempts < record.campaign.maxAttempts).slice(0, capacity);
5910
- 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
+ }
5911
6180
  for (const recipient of candidates) {
5912
6181
  const attempt = {
5913
6182
  campaignId,
@@ -5933,12 +6202,13 @@ var createVoiceCampaign = (options) => {
5933
6202
  attempt.externalCallId = dialerResult.externalCallId;
5934
6203
  attempt.metadata = dialerResult.metadata;
5935
6204
  attempt.status = dialerResult.status ?? "running";
5936
- attempt.updatedAt = Date.now();
6205
+ attempt.updatedAt = now();
5937
6206
  } catch (error) {
5938
- attempt.completedAt = Date.now();
6207
+ const failedAt = now();
6208
+ attempt.completedAt = failedAt;
5939
6209
  attempt.error = error instanceof Error ? error.message : String(error);
5940
6210
  attempt.status = "failed";
5941
- attempt.updatedAt = Date.now();
6211
+ attempt.updatedAt = failedAt;
5942
6212
  recipient.error = attempt.error;
5943
6213
  recipient.status = recipient.attempts >= record.campaign.maxAttempts ? "failed" : "pending";
5944
6214
  result.errors.push({
@@ -6255,6 +6525,142 @@ var runVoiceCampaignProof = async (options = {}) => {
6255
6525
  tick
6256
6526
  };
6257
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
+ };
6258
6664
  var escapeHtml3 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6259
6665
  var renderVoiceCampaignsHTML = (records, options = {}) => {
6260
6666
  const title = options.title ?? "Voice Campaigns";
@@ -6279,13 +6685,13 @@ var createVoiceCampaignRoutes = (options) => {
6279
6685
  const app = new Elysia2({ name: options.name ?? "absolutejs-voice-campaigns" }).get(path, async () => ({
6280
6686
  campaigns: await runtime.list(),
6281
6687
  summary: await runtime.summarize()
6282
- })).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 }) => {
6283
6689
  await runtime.remove(params.campaignId);
6284
6690
  return { ok: true };
6285
6691
  }).post(`${path}/:campaignId/recipients`, async ({ params, request }) => {
6286
6692
  const body = await readJsonBody(request);
6287
6693
  return runtime.addRecipients(params.campaignId, body.recipients ?? []);
6288
- }).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 ({
6289
6695
  params,
6290
6696
  request
6291
6697
  }) => runtime.completeAttempt(params.campaignId, params.attemptId, await readJsonBody(request)));
@@ -6936,11 +7342,14 @@ var createVoiceAgent = (options) => {
6936
7342
  const run = async (input) => {
6937
7343
  const messages = input.messages ?? createHistoryMessages(input.session, input.turn);
6938
7344
  const toolResults = [];
6939
- const system = typeof options.system === "function" ? await options.system({
7345
+ const baseSystem = typeof options.system === "function" ? await options.system({
6940
7346
  context: input.context,
6941
7347
  session: input.session,
6942
7348
  turn: input.turn
6943
7349
  }) : options.system;
7350
+ const system = [baseSystem, input.system].filter((value) => Boolean(value?.trim())).join(`
7351
+
7352
+ `) || undefined;
6944
7353
  let output = {};
6945
7354
  for (let round = 0;round <= maxToolRounds; round += 1) {
6946
7355
  const modelStartedAt = Date.now();
@@ -7994,7 +8403,25 @@ var createVoiceAssistant = (options) => {
7994
8403
  trace: options.trace,
7995
8404
  tools: variant.tools ?? baseModelOptions.tools
7996
8405
  }) : agent;
7997
- const runResult = await runner.run(input) ?? {};
8406
+ const liveOpsInstruction = input.liveOps?.injectedInstruction?.trim();
8407
+ if (liveOpsInstruction) {
8408
+ await appendAssistantTrace({
8409
+ assistantId: options.id,
8410
+ event: {
8411
+ action: "instruction-injected",
8412
+ artifactPlan: artifactPlanName,
8413
+ instruction: liveOpsInstruction
8414
+ },
8415
+ session: input.session,
8416
+ trace: options.trace,
8417
+ turnId: input.turn.id,
8418
+ type: "assistant.run"
8419
+ });
8420
+ }
8421
+ const runResult = await runner.run({
8422
+ ...input,
8423
+ system: liveOpsInstruction ? `Operator instruction for this turn: ${liveOpsInstruction}` : undefined
8424
+ }) ?? {};
7998
8425
  const result = runResult;
7999
8426
  const guarded = await options.guardrails?.afterTurn?.({
8000
8427
  ...guardrailInput,
@@ -15144,6 +15571,308 @@ var createVoiceLiveLatencyRoutes = (options) => {
15144
15571
  }
15145
15572
  return routes;
15146
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
+ };
15147
15876
  // src/turnQuality.ts
15148
15877
  import { Elysia as Elysia24 } from "elysia";
15149
15878
  var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
@@ -20192,8 +20921,8 @@ var createVoiceProviderCapabilityRoutes = (options) => {
20192
20921
  // src/resilienceRoutes.ts
20193
20922
  import { Elysia as Elysia33 } from "elysia";
20194
20923
  var escapeHtml34 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
20195
- var getString11 = (value) => typeof value === "string" ? value : undefined;
20196
- 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;
20197
20926
  var getBoolean2 = (value) => value === true;
20198
20927
  var isProviderStatus2 = (value) => value === "error" || value === "fallback" || value === "success";
20199
20928
  var listVoiceRoutingEvents = (events) => {
@@ -20202,27 +20931,27 @@ var listVoiceRoutingEvents = (events) => {
20202
20931
  if (event.type !== "session.error") {
20203
20932
  continue;
20204
20933
  }
20205
- const provider = getString11(event.payload.provider);
20934
+ const provider = getString12(event.payload.provider);
20206
20935
  const providerStatus = isProviderStatus2(event.payload.providerStatus) ? event.payload.providerStatus : undefined;
20207
20936
  if (!provider || !providerStatus) {
20208
20937
  continue;
20209
20938
  }
20210
- const kind = getString11(event.payload.kind);
20939
+ const kind = getString12(event.payload.kind);
20211
20940
  routingEvents.push({
20212
20941
  at: event.at,
20213
- attempt: getNumber6(event.payload.attempt),
20214
- elapsedMs: getNumber6(event.payload.elapsedMs),
20215
- error: getString11(event.payload.error),
20216
- 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),
20217
20946
  kind: kind === "stt" || kind === "tts" ? kind : "llm",
20218
- latencyBudgetMs: getNumber6(event.payload.latencyBudgetMs),
20219
- operation: getString11(event.payload.operation),
20947
+ latencyBudgetMs: getNumber7(event.payload.latencyBudgetMs),
20948
+ operation: getString12(event.payload.operation),
20220
20949
  provider,
20221
- routing: getString11(event.payload.routing),
20222
- selectedProvider: getString11(event.payload.selectedProvider),
20950
+ routing: getString12(event.payload.routing),
20951
+ selectedProvider: getString12(event.payload.selectedProvider),
20223
20952
  sessionId: event.sessionId,
20224
20953
  status: providerStatus,
20225
- suppressionRemainingMs: getNumber6(event.payload.suppressionRemainingMs),
20954
+ suppressionRemainingMs: getNumber7(event.payload.suppressionRemainingMs),
20226
20955
  timedOut: getBoolean2(event.payload.timedOut),
20227
20956
  turnId: event.turnId
20228
20957
  });
@@ -20719,6 +21448,7 @@ var readinessGateCodes = {
20719
21448
  "Audit evidence": "voice.readiness.audit_evidence",
20720
21449
  "Audit sink delivery": "voice.readiness.audit_sink_delivery",
20721
21450
  "Barge-in interruption proof": "voice.readiness.barge_in_interruption",
21451
+ "Campaign readiness proof": "voice.readiness.campaign_readiness",
20722
21452
  "Carrier readiness": "voice.readiness.carrier_readiness",
20723
21453
  "Delivery runtime": "voice.readiness.delivery_runtime",
20724
21454
  "Handoff delivery": "voice.readiness.handoff_delivery",
@@ -20832,6 +21562,12 @@ var resolveBargeInReports = async (options, input) => {
20832
21562
  }
20833
21563
  return typeof options.bargeInReports === "function" ? await options.bargeInReports(input) : options.bargeInReports;
20834
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
+ };
20835
21571
  var resolveProofSources = async (options, input) => {
20836
21572
  if (options.proofSources === false || options.proofSources === undefined) {
20837
21573
  return;
@@ -21052,8 +21788,8 @@ var summarizeLiveLatency = (events, options) => {
21052
21788
  warnings
21053
21789
  };
21054
21790
  };
21055
- var getString12 = (value) => typeof value === "string" ? value : undefined;
21056
- 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;
21057
21793
  var voiceOperationsRecordHref = (base, sessionId) => {
21058
21794
  const encoded = encodeURIComponent(sessionId);
21059
21795
  if (base.includes(":sessionId")) {
@@ -21064,7 +21800,7 @@ var voiceOperationsRecordHref = (base, sessionId) => {
21064
21800
  var buildOperationsRecordLinks = (input) => {
21065
21801
  const failedSessionSet = new Set(input.failedSessionIds);
21066
21802
  const providerErrors = input.events.filter((event) => event.type === "session.error" && (event.payload.providerStatus === "error" || typeof event.payload.error === "string")).map((event) => ({
21067
- detail: getString12(event.payload.error),
21803
+ detail: getString13(event.payload.error),
21068
21804
  href: voiceOperationsRecordHref(input.base, event.sessionId),
21069
21805
  label: "Open provider error operations record",
21070
21806
  sessionId: event.sessionId,
@@ -21072,7 +21808,7 @@ var buildOperationsRecordLinks = (input) => {
21072
21808
  }));
21073
21809
  const failingLatency = input.events.filter((event) => event.type === "client.live_latency").map((event) => ({
21074
21810
  event,
21075
- latencyMs: getNumber7(event.payload.latencyMs) ?? getNumber7(event.payload.elapsedMs)
21811
+ latencyMs: getNumber8(event.payload.latencyMs) ?? getNumber8(event.payload.elapsedMs)
21076
21812
  })).filter((entry) => entry.latencyMs !== undefined && entry.latencyMs > input.liveLatencyWarnAfterMs).map(({ event, latencyMs }) => ({
21077
21813
  detail: `${latencyMs}ms live latency`,
21078
21814
  href: voiceOperationsRecordHref(input.base, event.sessionId),
@@ -21118,6 +21854,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21118
21854
  phoneAgentSmokes,
21119
21855
  reconnectContracts,
21120
21856
  bargeInReports,
21857
+ campaignReadiness,
21121
21858
  proofSources
21122
21859
  ] = await Promise.all([
21123
21860
  evaluateVoiceQuality({ events }),
@@ -21150,6 +21887,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21150
21887
  resolvePhoneAgentSmokes(options, { query, request }),
21151
21888
  resolveReconnectContracts(options, { query, request }),
21152
21889
  resolveBargeInReports(options, { query, request }),
21890
+ resolveCampaignReadiness(options, { query, request }),
21153
21891
  resolveProofSources(options, { query, request })
21154
21892
  ]);
21155
21893
  const deliveryRuntime = summarizeDeliveryRuntime(deliveryRuntimeSummary);
@@ -21336,6 +22074,12 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21336
22074
  total: bargeInReports.reduce((total, report) => total + report.total, 0),
21337
22075
  warnings: bargeInReports.filter((report) => report.status === "warn").length
21338
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;
21339
22083
  if (agentSquadContractSummary) {
21340
22084
  checks.push({
21341
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.`,
@@ -21457,6 +22201,24 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21457
22201
  ]
21458
22202
  });
21459
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
+ }
21460
22222
  if (audit) {
21461
22223
  const missingLabels = audit.missing.map((requirement) => requirement.label ?? requirement.type);
21462
22224
  checks.push({
@@ -21566,6 +22328,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21566
22328
  audit: "/audit",
21567
22329
  auditDeliveries: "/audit",
21568
22330
  bargeIn: "/barge-in",
22331
+ campaignReadiness: "/api/voice/campaigns/readiness-proof",
21569
22332
  carriers: "/carriers",
21570
22333
  deliveryRuntime: "/delivery-runtime",
21571
22334
  handoffs: "/handoffs",
@@ -21592,6 +22355,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
21592
22355
  audit,
21593
22356
  auditDeliveries,
21594
22357
  bargeIn: bargeInSummary,
22358
+ campaignReadiness: campaignReadinessSummary,
21595
22359
  carriers: carrierSummary,
21596
22360
  deliveryRuntime,
21597
22361
  handoffs: {
@@ -21717,6 +22481,7 @@ var profileSurfaceLabels = {
21717
22481
  },
21718
22482
  "phone-agent": {
21719
22483
  auditDeliveries: "audit delivery queue configured",
22484
+ campaignReadiness: "campaign readiness proof configured",
21720
22485
  carriers: "carrier readiness configured",
21721
22486
  deliveryRuntime: "delivery runtime configured",
21722
22487
  phoneAgentSmokes: "phone-agent smoke proof configured",
@@ -21740,6 +22505,7 @@ var profileRequiredKeys = {
21740
22505
  "phone-agent": [
21741
22506
  "carriers",
21742
22507
  "phoneAgentSmokes",
22508
+ "campaignReadiness",
21743
22509
  "providerRoutingContracts",
21744
22510
  "auditDeliveries",
21745
22511
  "traceDeliveries",
@@ -21752,6 +22518,7 @@ var configuredProfileKeys = (options) => {
21752
22518
  "audit",
21753
22519
  "auditDeliveries",
21754
22520
  "bargeInReports",
22521
+ "campaignReadiness",
21755
22522
  "carriers",
21756
22523
  "deliveryRuntime",
21757
22524
  "opsActionHistory",
@@ -21848,6 +22615,12 @@ var profileExplanation = (profile, options, links) => {
21848
22615
  key: "phoneAgentSmokes",
21849
22616
  label: "Phone agent smoke"
21850
22617
  },
22618
+ {
22619
+ configured: isConfigured(options.campaignReadiness),
22620
+ href: links.campaignReadiness,
22621
+ key: "campaignReadiness",
22622
+ label: "Campaign readiness proof"
22623
+ },
21851
22624
  {
21852
22625
  configured: true,
21853
22626
  href: links.handoffs,
@@ -21943,6 +22716,7 @@ var createVoiceReadinessProfile = (profile, options = {}) => {
21943
22716
  if (profile === "phone-agent") {
21944
22717
  const links2 = mergeLinks({
21945
22718
  auditDeliveries: "/audit/deliveries",
22719
+ campaignReadiness: "/api/voice/campaigns/readiness-proof",
21946
22720
  carriers: "/carriers",
21947
22721
  deliveryRuntime: "/delivery-runtime",
21948
22722
  handoffs: "/handoffs",
@@ -21954,6 +22728,7 @@ var createVoiceReadinessProfile = (profile, options = {}) => {
21954
22728
  }, options.links);
21955
22729
  return withDefined({
21956
22730
  auditDeliveries: options.auditDeliveries,
22731
+ campaignReadiness: options.campaignReadiness,
21957
22732
  carriers: options.carriers,
21958
22733
  deliveryRuntime: options.deliveryRuntime,
21959
22734
  gate: options.gate,
@@ -22506,11 +23281,11 @@ import { Elysia as Elysia38 } from "elysia";
22506
23281
  // src/traceTimeline.ts
22507
23282
  import { Elysia as Elysia37 } from "elysia";
22508
23283
  var escapeHtml38 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
22509
- var getString13 = (value) => typeof value === "string" && value.trim() ? value : undefined;
22510
- 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;
22511
23286
  var firstString3 = (payload, keys) => {
22512
23287
  for (const key of keys) {
22513
- const value = getString13(payload[key]);
23288
+ const value = getString14(payload[key]);
22514
23289
  if (value) {
22515
23290
  return value;
22516
23291
  }
@@ -22519,7 +23294,7 @@ var firstString3 = (payload, keys) => {
22519
23294
  };
22520
23295
  var firstNumber3 = (payload, keys) => {
22521
23296
  for (const key of keys) {
22522
- const value = getNumber8(payload[key]);
23297
+ const value = getNumber9(payload[key]);
22523
23298
  if (value !== undefined) {
22524
23299
  return value;
22525
23300
  }
@@ -22539,7 +23314,7 @@ var eventStatus = (event) => firstString3(event.payload, [
22539
23314
  "type",
22540
23315
  "reason"
22541
23316
  ]);
22542
- var eventElapsedMs = (event) => firstNumber3(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
23317
+ var eventElapsedMs2 = (event) => firstNumber3(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
22543
23318
  var timelineLabel = (event) => {
22544
23319
  switch (event.type) {
22545
23320
  case "call.lifecycle":
@@ -22547,15 +23322,15 @@ var timelineLabel = (event) => {
22547
23322
  case "turn.transcript":
22548
23323
  return event.payload.isFinal === true ? "Final transcript" : "Partial transcript";
22549
23324
  case "turn.committed":
22550
- return `Committed turn${getString13(event.payload.reason) ? ` (${getString13(event.payload.reason)})` : ""}`;
23325
+ return `Committed turn${getString14(event.payload.reason) ? ` (${getString14(event.payload.reason)})` : ""}`;
22551
23326
  case "turn.assistant":
22552
23327
  return "Assistant reply";
22553
23328
  case "agent.model":
22554
23329
  return `Model call${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
22555
23330
  case "agent.tool":
22556
- return `Tool ${getString13(event.payload.toolName) ?? "call"}`;
23331
+ return `Tool ${getString14(event.payload.toolName) ?? "call"}`;
22557
23332
  case "agent.handoff":
22558
- 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)}` : ""}`;
22559
23334
  case "assistant.run":
22560
23335
  return `Assistant run${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
22561
23336
  case "assistant.guardrail":
@@ -22563,13 +23338,13 @@ var timelineLabel = (event) => {
22563
23338
  case "call.handoff":
22564
23339
  return `Call handoff ${eventStatus(event) ?? ""}`.trim();
22565
23340
  case "client.live_latency":
22566
- return `Live latency${eventElapsedMs(event) !== undefined ? ` ${eventElapsedMs(event)}ms` : ""}`;
23341
+ return `Live latency${eventElapsedMs2(event) !== undefined ? ` ${eventElapsedMs2(event)}ms` : ""}`;
22567
23342
  case "session.error":
22568
- return `Error${getString13(event.payload.error) ? `: ${getString13(event.payload.error)}` : ""}`;
23343
+ return `Error${getString14(event.payload.error) ? `: ${getString14(event.payload.error)}` : ""}`;
22569
23344
  case "turn.cost":
22570
23345
  return "Cost telemetry";
22571
23346
  case "turn_latency.stage":
22572
- return `Latency ${getString13(event.payload.stage) ?? "stage"}`;
23347
+ return `Latency ${getString14(event.payload.stage) ?? "stage"}`;
22573
23348
  case "workflow.contract":
22574
23349
  return `Workflow contract ${eventStatus(event) ?? ""}`.trim();
22575
23350
  default:
@@ -22601,7 +23376,7 @@ var summarizeProviders = (events) => {
22601
23376
  }
22602
23377
  const entry = getEntry(provider);
22603
23378
  const status = eventStatus(event);
22604
- const elapsedMs = eventElapsedMs(event);
23379
+ const elapsedMs = eventElapsedMs2(event);
22605
23380
  entry.eventCount += 1;
22606
23381
  if (elapsedMs !== undefined) {
22607
23382
  entry.elapsed.push(elapsedMs);
@@ -22647,7 +23422,7 @@ var summarizeVoiceTraceTimeline = (events, options = {}) => {
22647
23422
  evaluation,
22648
23423
  events: sorted.map((event) => ({
22649
23424
  at: event.at,
22650
- elapsedMs: eventElapsedMs(event),
23425
+ elapsedMs: eventElapsedMs2(event),
22651
23426
  id: event.id,
22652
23427
  label: timelineLabel(event),
22653
23428
  offsetMs: Math.max(0, event.at - startedAt),
@@ -22762,26 +23537,32 @@ var createVoiceTraceTimelineRoutes = (options) => {
22762
23537
  };
22763
23538
 
22764
23539
  // src/operationsRecord.ts
22765
- var getString14 = (value) => typeof value === "string" ? value : undefined;
22766
- 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;
22767
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;
22768
23549
  var toHandoff = (event) => ({
22769
23550
  at: event.at,
22770
- fromAgentId: getString14(event.payload.fromAgentId),
23551
+ fromAgentId: getString15(event.payload.fromAgentId),
22771
23552
  metadata: event.payload.metadata && typeof event.payload.metadata === "object" && !Array.isArray(event.payload.metadata) ? event.payload.metadata : undefined,
22772
- reason: getString14(event.payload.reason),
22773
- status: getString14(event.payload.status),
22774
- summary: getString14(event.payload.summary),
22775
- 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),
22776
23557
  turnId: event.turnId
22777
23558
  });
22778
23559
  var toTool = (event) => ({
22779
23560
  at: event.at,
22780
- elapsedMs: getNumber9(event.payload.elapsedMs),
22781
- error: getString14(event.payload.error),
22782
- status: getString14(event.payload.status),
22783
- toolCallId: getString14(event.payload.toolCallId),
22784
- 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),
22785
23566
  turnId: event.turnId
22786
23567
  });
22787
23568
  var resolveOutcome4 = (events) => {
@@ -22813,6 +23594,12 @@ var buildVoiceOperationsRecord = async (options) => {
22813
23594
  });
22814
23595
  const rawAuditEvents = options.audit ? filterVoiceAuditEvents(await options.audit.list({ sessionId: options.sessionId })) : undefined;
22815
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;
22816
23603
  return {
22817
23604
  audit: auditEvents ? {
22818
23605
  error: countOutcome(auditEvents, "error"),
@@ -22823,12 +23610,34 @@ var buildVoiceOperationsRecord = async (options) => {
22823
23610
  } : undefined,
22824
23611
  checkedAt: Date.now(),
22825
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,
22826
23622
  outcome: resolveOutcome4(traceEvents),
22827
23623
  providers: timelineSession?.providers ?? [],
22828
23624
  replay,
23625
+ reviews: reviews ? {
23626
+ failed: reviews.filter((review) => !review.summary.pass).length,
23627
+ reviews,
23628
+ total: reviews.length
23629
+ } : undefined,
22829
23630
  sessionId: options.sessionId,
22830
23631
  status: timelineSession?.status ?? "healthy",
22831
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,
22832
23641
  timeline: timelineSession?.events ?? [],
22833
23642
  tools: traceEvents.filter((event) => event.type === "agent.tool").map(toTool),
22834
23643
  traceEvents
@@ -22840,18 +23649,24 @@ var renderVoiceOperationsRecordHTML = (record, options = {}) => {
22840
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>';
22841
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>";
22842
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>";
22843
23655
  const snippet = escapeHtml39(`app.use(
22844
23656
  createVoiceOperationsRecordRoutes({
22845
23657
  audit: auditStore,
23658
+ integrationEvents: opsEvents,
22846
23659
  htmlPath: '/voice-ops/:sessionId',
22847
23660
  path: '/api/voice-ops/:sessionId',
22848
23661
  redact: {
22849
23662
  keys: ['authorization', 'apiKey', 'token']
22850
23663
  },
22851
- store: traceStore
23664
+ reviews: callReviews,
23665
+ store: traceStore,
23666
+ tasks: opsTasks
22852
23667
  })
22853
23668
  );`);
22854
- 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>`;
22855
23670
  };
22856
23671
  var createVoiceOperationsRecordRoutes = (options) => {
22857
23672
  const path = options.path ?? "/api/voice-operations/:sessionId";
@@ -22863,9 +23678,12 @@ var createVoiceOperationsRecordRoutes = (options) => {
22863
23678
  audit: options.audit,
22864
23679
  evaluation: options.evaluation,
22865
23680
  events: options.events,
23681
+ integrationEvents: options.integrationEvents,
22866
23682
  redact: options.redact,
23683
+ reviews: options.reviews,
22867
23684
  sessionId,
22868
- store: options.store
23685
+ store: options.store,
23686
+ tasks: options.tasks
22869
23687
  });
22870
23688
  const getSessionId = (params) => params.sessionId ?? "";
22871
23689
  routes.get(path, async ({ params }) => Response.json(await buildRecord(getSessionId(params))));
@@ -23733,8 +24551,8 @@ var createVoiceTTSProviderRouter = (options) => {
23733
24551
  // src/traceDeliveryRoutes.ts
23734
24552
  import { Elysia as Elysia41 } from "elysia";
23735
24553
  var escapeHtml41 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
23736
- var getString15 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
23737
- var getNumber10 = (value) => {
24554
+ var getString16 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
24555
+ var getNumber11 = (value) => {
23738
24556
  if (typeof value === "number" && Number.isFinite(value)) {
23739
24557
  return value;
23740
24558
  }
@@ -23745,13 +24563,13 @@ var getNumber10 = (value) => {
23745
24563
  return;
23746
24564
  };
23747
24565
  var parseStatus2 = (value) => {
23748
- const text = getString15(value);
24566
+ const text = getString16(value);
23749
24567
  return text === "pending" || text === "delivered" || text === "failed" || text === "skipped" || text === "all" ? text : undefined;
23750
24568
  };
23751
24569
  var resolveVoiceTraceDeliveryFilter = (query = {}, base = {}) => ({
23752
24570
  ...base,
23753
- limit: getNumber10(query.limit) ?? base.limit,
23754
- q: getString15(query.q) ?? base.q,
24571
+ limit: getNumber11(query.limit) ?? base.limit,
24572
+ q: getString16(query.q) ?? base.q,
23755
24573
  status: parseStatus2(query.status) ?? base.status
23756
24574
  });
23757
24575
  var deliverySearchText2 = (delivery) => [
@@ -25490,6 +26308,7 @@ export {
25490
26308
  runVoiceProviderRoutingContract,
25491
26309
  runVoicePhoneAgentProductionSmokeContract,
25492
26310
  runVoiceOutcomeContractSuite,
26311
+ runVoiceCampaignReadinessProof,
25493
26312
  runVoiceCampaignProof,
25494
26313
  runVoiceCampaignDialerProof,
25495
26314
  runVoiceAgentSquadContract,
@@ -25539,6 +26358,7 @@ export {
25539
26358
  renderVoiceOpsActionHistoryHTML,
25540
26359
  renderVoiceOperationsRecordHTML,
25541
26360
  renderVoiceLiveLatencyHTML,
26361
+ renderVoiceLatencySLOMarkdown,
25542
26362
  renderVoiceHandoffHealthHTML,
25543
26363
  renderVoiceEvalHTML,
25544
26364
  renderVoiceEvalBaselineHTML,
@@ -25579,6 +26399,7 @@ export {
25579
26399
  listVoiceRoutingEvents,
25580
26400
  listVoiceOpsTasks,
25581
26401
  isVoiceOpsTaskOverdue,
26402
+ importVoiceCampaignRecipients,
25582
26403
  heartbeatVoiceOpsTask,
25583
26404
  hasVoiceOpsTaskSLABreach,
25584
26405
  getVoiceLiveOpsControlStatus,
@@ -25862,6 +26683,7 @@ export {
25862
26683
  buildVoiceOpsActionHistoryReport,
25863
26684
  buildVoiceOperationsRecord,
25864
26685
  buildVoiceLiveOpsControlState,
26686
+ buildVoiceLatencySLOGate,
25865
26687
  buildVoiceIncidentBundle,
25866
26688
  buildVoiceDiagnosticsMarkdown,
25867
26689
  buildVoiceDemoReadyReport,
@@ -25874,6 +26696,7 @@ export {
25874
26696
  buildVoiceAuditDeliveryReport,
25875
26697
  assignVoiceOpsTask,
25876
26698
  assertVoiceProviderRoutingContract,
26699
+ assertVoiceLatencySLOGate,
25877
26700
  assertVoiceAgentSquadContract,
25878
26701
  applyVoiceTelephonyOutcome,
25879
26702
  applyVoiceOpsTaskPolicy,