@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/README.md +112 -1
- package/dist/agent.d.ts +1 -0
- package/dist/angular/voice-live-ops.service.d.ts +1 -1
- package/dist/campaign.d.ts +132 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +896 -73
- 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/testing/index.js +23 -0
- package/dist/types.d.ts +10 -0
- package/dist/vue/useVoiceLiveOps.d.ts +1 -1
- package/package.json +1 -1
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
|
-
|
|
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 =
|
|
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
|
|
5910
|
-
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
|
+
}
|
|
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 =
|
|
6205
|
+
attempt.updatedAt = now();
|
|
5937
6206
|
} catch (error) {
|
|
5938
|
-
|
|
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 =
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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
|
|
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
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
20195
|
-
var
|
|
20196
|
-
var
|
|
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 =
|
|
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 =
|
|
20939
|
+
const kind = getString12(event.payload.kind);
|
|
20211
20940
|
routingEvents.push({
|
|
20212
20941
|
at: event.at,
|
|
20213
|
-
attempt:
|
|
20214
|
-
elapsedMs:
|
|
20215
|
-
error:
|
|
20216
|
-
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:
|
|
20219
|
-
operation:
|
|
20947
|
+
latencyBudgetMs: getNumber7(event.payload.latencyBudgetMs),
|
|
20948
|
+
operation: getString12(event.payload.operation),
|
|
20220
20949
|
provider,
|
|
20221
|
-
routing:
|
|
20222
|
-
selectedProvider:
|
|
20950
|
+
routing: getString12(event.payload.routing),
|
|
20951
|
+
selectedProvider: getString12(event.payload.selectedProvider),
|
|
20223
20952
|
sessionId: event.sessionId,
|
|
20224
20953
|
status: providerStatus,
|
|
20225
|
-
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
|
|
21056
|
-
var
|
|
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:
|
|
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:
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22509
|
-
var
|
|
22510
|
-
var
|
|
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 =
|
|
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 =
|
|
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
|
|
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${
|
|
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 ${
|
|
23331
|
+
return `Tool ${getString14(event.payload.toolName) ?? "call"}`;
|
|
22557
23332
|
case "agent.handoff":
|
|
22558
|
-
return `Agent handoff${
|
|
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${
|
|
23341
|
+
return `Live latency${eventElapsedMs2(event) !== undefined ? ` ${eventElapsedMs2(event)}ms` : ""}`;
|
|
22567
23342
|
case "session.error":
|
|
22568
|
-
return `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 ${
|
|
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 =
|
|
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:
|
|
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
|
|
22766
|
-
var
|
|
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:
|
|
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:
|
|
22773
|
-
status:
|
|
22774
|
-
summary:
|
|
22775
|
-
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:
|
|
22781
|
-
error:
|
|
22782
|
-
status:
|
|
22783
|
-
toolCallId:
|
|
22784
|
-
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
|
-
|
|
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
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
23736
|
-
var
|
|
23737
|
-
var
|
|
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 =
|
|
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:
|
|
23754
|
-
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,
|