@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/README.md +129 -1
- package/dist/agent.d.ts +15 -0
- package/dist/campaign.d.ts +132 -0
- package/dist/client/index.js +2 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +891 -74
- package/dist/latencySlo.d.ts +56 -0
- package/dist/operationsRecord.d.ts +30 -0
- package/dist/productionReadiness.d.ts +12 -0
- package/dist/readinessProfiles.d.ts +1 -0
- package/dist/svelte/index.js +2 -0
- package/dist/trace.d.ts +1 -1
- package/package.json +1 -1
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
|
-
|
|
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 =
|
|
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
|
|
5934
|
-
const
|
|
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 =
|
|
6205
|
+
attempt.updatedAt = now();
|
|
5961
6206
|
} catch (error) {
|
|
5962
|
-
|
|
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 =
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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
|
-
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
20240
|
-
var
|
|
20241
|
-
var
|
|
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 =
|
|
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 =
|
|
20978
|
+
const kind = getString12(event.payload.kind);
|
|
20256
20979
|
routingEvents.push({
|
|
20257
20980
|
at: event.at,
|
|
20258
|
-
attempt:
|
|
20259
|
-
elapsedMs:
|
|
20260
|
-
error:
|
|
20261
|
-
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:
|
|
20264
|
-
operation:
|
|
20986
|
+
latencyBudgetMs: getNumber7(event.payload.latencyBudgetMs),
|
|
20987
|
+
operation: getString12(event.payload.operation),
|
|
20265
20988
|
provider,
|
|
20266
|
-
routing:
|
|
20267
|
-
selectedProvider:
|
|
20989
|
+
routing: getString12(event.payload.routing),
|
|
20990
|
+
selectedProvider: getString12(event.payload.selectedProvider),
|
|
20268
20991
|
sessionId: event.sessionId,
|
|
20269
20992
|
status: providerStatus,
|
|
20270
|
-
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
|
|
21101
|
-
var
|
|
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:
|
|
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:
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22554
|
-
var
|
|
22555
|
-
var
|
|
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 =
|
|
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 =
|
|
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
|
|
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${
|
|
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 ${
|
|
23370
|
+
return `Tool ${getString14(event.payload.toolName) ?? "call"}`;
|
|
22602
23371
|
case "agent.handoff":
|
|
22603
|
-
return `Agent handoff${
|
|
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${
|
|
23380
|
+
return `Live latency${eventElapsedMs2(event) !== undefined ? ` ${eventElapsedMs2(event)}ms` : ""}`;
|
|
22612
23381
|
case "session.error":
|
|
22613
|
-
return `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 ${
|
|
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 =
|
|
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:
|
|
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
|
|
22811
|
-
var
|
|
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:
|
|
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:
|
|
22818
|
-
status:
|
|
22819
|
-
summary:
|
|
22820
|
-
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:
|
|
22826
|
-
error:
|
|
22827
|
-
status:
|
|
22828
|
-
toolCallId:
|
|
22829
|
-
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
|
-
|
|
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
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
23781
|
-
var
|
|
23782
|
-
var
|
|
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 =
|
|
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:
|
|
23799
|
-
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,
|