@absolutejs/voice 0.0.22-beta.107 → 0.0.22-beta.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10546,12 +10546,369 @@ var createVoiceAppKitRoutes = (options) => {
10546
10546
  };
10547
10547
  };
10548
10548
  var createVoiceAppKit = createVoiceAppKitRoutes;
10549
+ // src/campaign.ts
10550
+ import { Elysia as Elysia17 } from "elysia";
10551
+ var createId2 = () => crypto.randomUUID();
10552
+ var cloneRecord = (record) => ({
10553
+ attempts: record.attempts.map((attempt) => ({ ...attempt })),
10554
+ campaign: { ...record.campaign },
10555
+ recipients: record.recipients.map((recipient) => ({ ...recipient }))
10556
+ });
10557
+ var ensureRecord = async (store, campaignId) => {
10558
+ const record = await store.get(campaignId);
10559
+ if (!record) {
10560
+ throw new Error(`Voice campaign ${campaignId} was not found.`);
10561
+ }
10562
+ return cloneRecord(record);
10563
+ };
10564
+ var saveRecord = async (store, record) => {
10565
+ record.campaign.updatedAt = Date.now();
10566
+ await store.set(record.campaign.id, record);
10567
+ return record;
10568
+ };
10569
+ var activeAttemptCount = (record) => record.attempts.filter((attempt) => attempt.status === "queued" || attempt.status === "running").length;
10570
+ var maybeCompleteCampaign = (record) => {
10571
+ const unfinished = record.recipients.some((recipient) => recipient.status === "pending" || recipient.status === "queued");
10572
+ const active = activeAttemptCount(record) > 0;
10573
+ if (!unfinished && !active && record.campaign.status === "running") {
10574
+ record.campaign.status = "completed";
10575
+ }
10576
+ };
10577
+ var createVoiceMemoryCampaignStore = () => {
10578
+ const campaigns = new Map;
10579
+ return {
10580
+ get: (id) => {
10581
+ const record = campaigns.get(id);
10582
+ return record ? cloneRecord(record) : undefined;
10583
+ },
10584
+ list: () => [...campaigns.values()].map(cloneRecord).sort((left, right) => right.campaign.createdAt - left.campaign.createdAt),
10585
+ remove: (id) => {
10586
+ campaigns.delete(id);
10587
+ },
10588
+ set: (id, record) => {
10589
+ campaigns.set(id, cloneRecord(record));
10590
+ }
10591
+ };
10592
+ };
10593
+ var summarizeVoiceCampaigns = (records) => {
10594
+ const summary = {
10595
+ attempts: { failed: 0, queued: 0, running: 0, succeeded: 0, total: 0 },
10596
+ campaigns: {
10597
+ canceled: 0,
10598
+ completed: 0,
10599
+ draft: 0,
10600
+ paused: 0,
10601
+ running: 0,
10602
+ total: records.length
10603
+ },
10604
+ recipients: {
10605
+ canceled: 0,
10606
+ completed: 0,
10607
+ failed: 0,
10608
+ pending: 0,
10609
+ queued: 0,
10610
+ total: 0
10611
+ }
10612
+ };
10613
+ for (const record of records) {
10614
+ summary.campaigns[record.campaign.status] += 1;
10615
+ for (const recipient of record.recipients) {
10616
+ summary.recipients.total += 1;
10617
+ summary.recipients[recipient.status] += 1;
10618
+ }
10619
+ for (const attempt of record.attempts) {
10620
+ summary.attempts.total += 1;
10621
+ if (attempt.status === "queued" || attempt.status === "running") {
10622
+ summary.attempts[attempt.status] += 1;
10623
+ }
10624
+ if (attempt.status === "failed" || attempt.status === "succeeded") {
10625
+ summary.attempts[attempt.status] += 1;
10626
+ }
10627
+ }
10628
+ }
10629
+ return summary;
10630
+ };
10631
+ var createVoiceCampaign = (options) => {
10632
+ const { store } = options;
10633
+ return {
10634
+ addRecipients: async (campaignId, recipients) => {
10635
+ const record = await ensureRecord(store, campaignId);
10636
+ const at = Date.now();
10637
+ record.recipients.push(...recipients.map((recipient) => ({
10638
+ attempts: 0,
10639
+ createdAt: at,
10640
+ id: recipient.id ?? createId2(),
10641
+ metadata: recipient.metadata,
10642
+ name: recipient.name,
10643
+ phone: recipient.phone,
10644
+ status: "pending",
10645
+ updatedAt: at,
10646
+ variables: recipient.variables
10647
+ })));
10648
+ return saveRecord(store, record);
10649
+ },
10650
+ cancel: async (campaignId) => {
10651
+ const record = await ensureRecord(store, campaignId);
10652
+ const at = Date.now();
10653
+ record.campaign.status = "canceled";
10654
+ for (const recipient of record.recipients) {
10655
+ if (recipient.status === "pending" || recipient.status === "queued") {
10656
+ recipient.status = "canceled";
10657
+ recipient.updatedAt = at;
10658
+ }
10659
+ }
10660
+ for (const attempt of record.attempts) {
10661
+ if (attempt.status === "queued" || attempt.status === "running") {
10662
+ attempt.status = "canceled";
10663
+ attempt.completedAt = at;
10664
+ attempt.updatedAt = at;
10665
+ }
10666
+ }
10667
+ return saveRecord(store, record);
10668
+ },
10669
+ completeAttempt: async (campaignId, attemptId, result) => {
10670
+ const record = await ensureRecord(store, campaignId);
10671
+ const attempt = record.attempts.find((item) => item.id === attemptId);
10672
+ if (!attempt) {
10673
+ throw new Error(`Voice campaign attempt ${attemptId} was not found.`);
10674
+ }
10675
+ const recipient = record.recipients.find((item) => item.id === attempt.recipientId);
10676
+ const at = Date.now();
10677
+ attempt.completedAt = at;
10678
+ attempt.error = result.error;
10679
+ attempt.externalCallId = result.externalCallId ?? attempt.externalCallId;
10680
+ attempt.metadata = { ...attempt.metadata, ...result.metadata };
10681
+ attempt.status = result.status === "succeeded" ? "succeeded" : "failed";
10682
+ attempt.updatedAt = at;
10683
+ if (recipient) {
10684
+ recipient.completedAt = result.status === "succeeded" ? at : undefined;
10685
+ recipient.error = result.error;
10686
+ recipient.status = result.status === "succeeded" ? "completed" : recipient.attempts >= record.campaign.maxAttempts ? "failed" : "pending";
10687
+ recipient.updatedAt = at;
10688
+ }
10689
+ maybeCompleteCampaign(record);
10690
+ return saveRecord(store, record);
10691
+ },
10692
+ create: async (input) => {
10693
+ const at = Date.now();
10694
+ const campaign = {
10695
+ createdAt: at,
10696
+ description: input.description,
10697
+ id: input.id ?? createId2(),
10698
+ maxAttempts: input.maxAttempts ?? 1,
10699
+ maxConcurrentAttempts: input.maxConcurrentAttempts ?? 1,
10700
+ metadata: input.metadata,
10701
+ name: input.name,
10702
+ status: "draft",
10703
+ updatedAt: at
10704
+ };
10705
+ const record = { attempts: [], campaign, recipients: [] };
10706
+ await store.set(campaign.id, record);
10707
+ return record;
10708
+ },
10709
+ enqueue: async (campaignId) => {
10710
+ const record = await ensureRecord(store, campaignId);
10711
+ const at = Date.now();
10712
+ record.campaign.status = "running";
10713
+ for (const recipient of record.recipients) {
10714
+ if (recipient.status === "pending") {
10715
+ recipient.status = "queued";
10716
+ recipient.updatedAt = at;
10717
+ }
10718
+ }
10719
+ return saveRecord(store, record);
10720
+ },
10721
+ get: async (campaignId) => await store.get(campaignId),
10722
+ list: async () => await store.list(),
10723
+ pause: async (campaignId) => {
10724
+ const record = await ensureRecord(store, campaignId);
10725
+ record.campaign.status = "paused";
10726
+ return saveRecord(store, record);
10727
+ },
10728
+ remove: async (campaignId) => {
10729
+ await store.remove(campaignId);
10730
+ },
10731
+ resume: async (campaignId) => {
10732
+ const record = await ensureRecord(store, campaignId);
10733
+ record.campaign.status = "running";
10734
+ return saveRecord(store, record);
10735
+ },
10736
+ summarize: async () => summarizeVoiceCampaigns(await store.list()),
10737
+ tick: async (campaignId) => {
10738
+ const record = await ensureRecord(store, campaignId);
10739
+ const result = {
10740
+ attempted: 0,
10741
+ campaignId,
10742
+ errors: [],
10743
+ started: []
10744
+ };
10745
+ if (record.campaign.status !== "running") {
10746
+ return result;
10747
+ }
10748
+ const capacity = Math.max(0, record.campaign.maxConcurrentAttempts - activeAttemptCount(record));
10749
+ const candidates = record.recipients.filter((recipient) => recipient.status === "queued" && recipient.attempts < record.campaign.maxAttempts).slice(0, capacity);
10750
+ const at = Date.now();
10751
+ for (const recipient of candidates) {
10752
+ const attempt = {
10753
+ campaignId,
10754
+ createdAt: at,
10755
+ id: createId2(),
10756
+ recipientId: recipient.id,
10757
+ startedAt: at,
10758
+ status: options.dialer ? "running" : "queued",
10759
+ updatedAt: at
10760
+ };
10761
+ recipient.attempts += 1;
10762
+ recipient.updatedAt = at;
10763
+ record.attempts.push(attempt);
10764
+ result.started.push(attempt);
10765
+ result.attempted += 1;
10766
+ if (options.dialer) {
10767
+ try {
10768
+ const dialerResult = await options.dialer({
10769
+ attempt,
10770
+ campaign: record.campaign,
10771
+ recipient
10772
+ });
10773
+ attempt.externalCallId = dialerResult.externalCallId;
10774
+ attempt.metadata = dialerResult.metadata;
10775
+ attempt.status = dialerResult.status ?? "running";
10776
+ attempt.updatedAt = Date.now();
10777
+ } catch (error) {
10778
+ attempt.completedAt = Date.now();
10779
+ attempt.error = error instanceof Error ? error.message : String(error);
10780
+ attempt.status = "failed";
10781
+ attempt.updatedAt = Date.now();
10782
+ recipient.error = attempt.error;
10783
+ recipient.status = recipient.attempts >= record.campaign.maxAttempts ? "failed" : "pending";
10784
+ result.errors.push({
10785
+ error: attempt.error,
10786
+ recipientId: recipient.id
10787
+ });
10788
+ }
10789
+ }
10790
+ }
10791
+ maybeCompleteCampaign(record);
10792
+ await saveRecord(store, record);
10793
+ return result;
10794
+ }
10795
+ };
10796
+ };
10797
+ var defaultProofRecipients = () => [
10798
+ {
10799
+ id: "campaign-proof-recipient-1",
10800
+ name: "Proof Recipient One",
10801
+ phone: "+15550001001",
10802
+ variables: {
10803
+ firstName: "Ari",
10804
+ reason: "demo"
10805
+ }
10806
+ },
10807
+ {
10808
+ id: "campaign-proof-recipient-2",
10809
+ name: "Proof Recipient Two",
10810
+ phone: "+15550001002",
10811
+ variables: {
10812
+ firstName: "Sam",
10813
+ reason: "follow-up"
10814
+ }
10815
+ }
10816
+ ];
10817
+ var runVoiceCampaignProof = async (options = {}) => {
10818
+ const runtime = options.runtime ?? createVoiceCampaign({
10819
+ dialer: ({ attempt, recipient }) => ({
10820
+ externalCallId: `proof-call-${attempt.id}`,
10821
+ metadata: {
10822
+ mode: "simulation",
10823
+ phone: recipient.phone
10824
+ },
10825
+ status: "running"
10826
+ }),
10827
+ store: options.store ?? createVoiceMemoryCampaignStore()
10828
+ });
10829
+ const created = await runtime.create({
10830
+ description: "Synthetic outbound campaign proof.",
10831
+ id: `campaign-proof-${crypto.randomUUID()}`,
10832
+ maxAttempts: 1,
10833
+ maxConcurrentAttempts: 10,
10834
+ name: "AbsoluteJS Voice Campaign Proof",
10835
+ ...options.campaign
10836
+ });
10837
+ const recipients = await runtime.addRecipients(created.campaign.id, options.recipients ?? defaultProofRecipients());
10838
+ const campaign = await runtime.enqueue(recipients.campaign.id);
10839
+ const tick = await runtime.tick(campaign.campaign.id);
10840
+ if (options.completeAttempts !== false) {
10841
+ for (const attempt of tick.started) {
10842
+ await runtime.completeAttempt(campaign.campaign.id, attempt.id, {
10843
+ externalCallId: attempt.externalCallId,
10844
+ metadata: {
10845
+ proof: true
10846
+ },
10847
+ status: "succeeded"
10848
+ });
10849
+ }
10850
+ }
10851
+ const final = await runtime.get(campaign.campaign.id);
10852
+ if (!final) {
10853
+ throw new Error("Campaign proof did not persist the final campaign record.");
10854
+ }
10855
+ return {
10856
+ campaign,
10857
+ created,
10858
+ final,
10859
+ proof: "voice-campaign",
10860
+ recipients,
10861
+ summary: await runtime.summarize(),
10862
+ tick
10863
+ };
10864
+ };
10865
+ var escapeHtml18 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10866
+ var renderVoiceCampaignsHTML = (records, options = {}) => {
10867
+ const title = options.title ?? "Voice Campaigns";
10868
+ const rows = records.map((record) => `<tr><td>${escapeHtml18(record.campaign.name)}</td><td>${escapeHtml18(record.campaign.status)}</td><td>${String(record.recipients.length)}</td><td>${String(record.attempts.length)}</td><td>${new Date(record.campaign.updatedAt).toLocaleString()}</td></tr>`).join("");
10869
+ const summary = summarizeVoiceCampaigns(records);
10870
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml18(title)}</title><style>body{background:#111827;color:#f9fafb;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1080px;padding:32px}.hero{background:linear-gradient(135deg,rgba(251,146,60,.18),rgba(45,212,191,.12));border:1px solid #334155;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#fdba74;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:18px 0}.grid article,table{background:#172033;border:1px solid #334155;border-radius:18px}.grid article{padding:16px}.grid span{color:#aab5c0}.grid strong{display:block;font-size:2rem;margin:.25rem 0}table{border-collapse:collapse;overflow:hidden;width:100%}td,th{border-bottom:1px solid #334155;padding:12px;text-align:left}</style></head><body><main><section class="hero"><p class="eyebrow">Self-hosted outbound</p><h1>${escapeHtml18(title)}</h1><p>Campaign orchestration, recipients, attempts, retries, and outcomes without a hosted dialer dashboard.</p><section class="grid"><article><span>Campaigns</span><strong>${String(summary.campaigns.total)}</strong></article><article><span>Recipients</span><strong>${String(summary.recipients.total)}</strong></article><article><span>Attempts</span><strong>${String(summary.attempts.total)}</strong></article><article><span>Running</span><strong>${String(summary.campaigns.running)}</strong></article></section></section><table><thead><tr><th>Name</th><th>Status</th><th>Recipients</th><th>Attempts</th><th>Updated</th></tr></thead><tbody>${rows || '<tr><td colspan="5">No campaigns yet.</td></tr>'}</tbody></table></main></body></html>`;
10871
+ };
10872
+ var readJsonBody = async (request) => {
10873
+ const text = await request.text();
10874
+ return text.trim() ? JSON.parse(text) : {};
10875
+ };
10876
+ var createVoiceCampaignRoutes = (options) => {
10877
+ const runtime = createVoiceCampaign(options);
10878
+ const path = options.path ?? "/api/voice/campaigns";
10879
+ const htmlPath = options.htmlPath === undefined ? "/voice/campaigns" : options.htmlPath;
10880
+ const app = new Elysia17({ name: options.name ?? "absolutejs-voice-campaigns" }).get(path, async () => ({
10881
+ campaigns: await runtime.list(),
10882
+ summary: await runtime.summarize()
10883
+ })).post(path, async ({ request }) => runtime.create(await readJsonBody(request))).get(`${path}/:campaignId`, ({ params }) => runtime.get(params.campaignId)).delete(`${path}/:campaignId`, async ({ params }) => {
10884
+ await runtime.remove(params.campaignId);
10885
+ return { ok: true };
10886
+ }).post(`${path}/:campaignId/recipients`, async ({ params, request }) => {
10887
+ const body = await readJsonBody(request);
10888
+ return runtime.addRecipients(params.campaignId, body.recipients ?? []);
10889
+ }).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 ({
10890
+ params,
10891
+ request
10892
+ }) => runtime.completeAttempt(params.campaignId, params.attemptId, await readJsonBody(request)));
10893
+ if (htmlPath) {
10894
+ app.get(htmlPath, async () => {
10895
+ const records = await runtime.list();
10896
+ return new Response(renderVoiceCampaignsHTML(records, options), {
10897
+ headers: {
10898
+ "content-type": "text/html; charset=utf-8",
10899
+ ...options.headers
10900
+ }
10901
+ });
10902
+ });
10903
+ }
10904
+ return app;
10905
+ };
10549
10906
  // src/simulationSuite.ts
10550
- import { Elysia as Elysia19 } from "elysia";
10907
+ import { Elysia as Elysia20 } from "elysia";
10551
10908
 
10552
10909
  // src/outcomeContract.ts
10553
- import { Elysia as Elysia17 } from "elysia";
10554
- var escapeHtml18 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10910
+ import { Elysia as Elysia18 } from "elysia";
10911
+ var escapeHtml19 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10555
10912
  var getPayloadString = (event, key) => typeof event.payload[key] === "string" ? event.payload[key] : undefined;
10556
10913
  var toList = async (input) => Array.isArray(input) ? input : await input?.list() ?? [];
10557
10914
  var hydrateSessions = async (input) => {
@@ -10659,9 +11016,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
10659
11016
  const contracts = report.contracts.map((contract) => `<section class="contract ${contract.pass ? "pass" : "fail"}">
10660
11017
  <div class="contract-header">
10661
11018
  <div>
10662
- <p class="eyebrow">${escapeHtml18(contract.contractId)}</p>
10663
- <h2>${escapeHtml18(contract.label ?? contract.contractId)}</h2>
10664
- ${contract.description ? `<p>${escapeHtml18(contract.description)}</p>` : ""}
11019
+ <p class="eyebrow">${escapeHtml19(contract.contractId)}</p>
11020
+ <h2>${escapeHtml19(contract.label ?? contract.contractId)}</h2>
11021
+ ${contract.description ? `<p>${escapeHtml19(contract.description)}</p>` : ""}
10665
11022
  </div>
10666
11023
  <strong>${contract.pass ? "pass" : "fail"}</strong>
10667
11024
  </div>
@@ -10672,9 +11029,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
10672
11029
  <span>handoffs ${String(contract.matched.handoffs)}</span>
10673
11030
  <span>events ${String(contract.matched.integrationEvents)}</span>
10674
11031
  </div>
10675
- ${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml18(issue.message)}</li>`).join("")}</ul>` : ""}
11032
+ ${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml19(issue.message)}</li>`).join("")}</ul>` : ""}
10676
11033
  </section>`).join("");
10677
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml18(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.contract{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.14),rgba(14,165,233,.12))}.eyebrow{color:#7dd3fc;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0}.summary,.grid{display:flex;flex-wrap:wrap;gap:10px}.pill,.grid span{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.contract-header{display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.fail{color:#fca5a5}.contract.fail{border-color:rgba(248,113,113,.45)}li{margin:8px 0}@media(max-width:800px){main{padding:18px}.contract-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Business Outcome Verification</p><h1>${escapeHtml18(title)}</h1><div class="summary"><span class="pill ${report.status}">${report.status}</span><span class="pill">${String(report.passed)} passing</span><span class="pill">${String(report.failed)} failing</span><span class="pill">${String(report.total)} contracts</span></div></section>${contracts || '<section class="contract"><p>No outcome contracts configured.</p></section>'}</main></body></html>`;
11034
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml19(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.contract{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.14),rgba(14,165,233,.12))}.eyebrow{color:#7dd3fc;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0}.summary,.grid{display:flex;flex-wrap:wrap;gap:10px}.pill,.grid span{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.contract-header{display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.fail{color:#fca5a5}.contract.fail{border-color:rgba(248,113,113,.45)}li{margin:8px 0}@media(max-width:800px){main{padding:18px}.contract-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Business Outcome Verification</p><h1>${escapeHtml19(title)}</h1><div class="summary"><span class="pill ${report.status}">${report.status}</span><span class="pill">${String(report.passed)} passing</span><span class="pill">${String(report.failed)} failing</span><span class="pill">${String(report.total)} contracts</span></div></section>${contracts || '<section class="contract"><p>No outcome contracts configured.</p></section>'}</main></body></html>`;
10678
11035
  };
10679
11036
  var createVoiceOutcomeContractJSONHandler = (options) => async () => runVoiceOutcomeContractSuite(options);
10680
11037
  var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
@@ -10690,7 +11047,7 @@ var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
10690
11047
  var createVoiceOutcomeContractRoutes = (options) => {
10691
11048
  const path = options.path ?? "/api/outcome-contracts";
10692
11049
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
10693
- const routes = new Elysia17({
11050
+ const routes = new Elysia18({
10694
11051
  name: options.name ?? "absolutejs-voice-outcome-contracts"
10695
11052
  }).get(path, createVoiceOutcomeContractJSONHandler(options));
10696
11053
  if (htmlPath) {
@@ -10700,7 +11057,7 @@ var createVoiceOutcomeContractRoutes = (options) => {
10700
11057
  };
10701
11058
 
10702
11059
  // src/toolContract.ts
10703
- import { Elysia as Elysia18 } from "elysia";
11060
+ import { Elysia as Elysia19 } from "elysia";
10704
11061
 
10705
11062
  // src/toolRuntime.ts
10706
11063
  var toErrorMessage4 = (error) => error instanceof Error ? error.message : String(error);
@@ -10909,7 +11266,7 @@ var createDefaultTurn = (caseId) => ({
10909
11266
  });
10910
11267
  var defaultApi = {};
10911
11268
  var sameJSON = (left, right) => JSON.stringify(left) === JSON.stringify(right);
10912
- var escapeHtml19 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11269
+ var escapeHtml20 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10913
11270
  var evaluateExpectation = (input) => {
10914
11271
  const issues = [];
10915
11272
  const expect = input.expect;
@@ -11075,19 +11432,19 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
11075
11432
  const title = options.title ?? "Voice Tool Contracts";
11076
11433
  const contracts = report.contracts.map((contract) => {
11077
11434
  const cases = contract.cases.map((testCase) => `<tr>
11078
- <td>${escapeHtml19(testCase.label ?? testCase.caseId)}</td>
11435
+ <td>${escapeHtml20(testCase.label ?? testCase.caseId)}</td>
11079
11436
  <td class="${testCase.pass ? "pass" : "fail"}">${testCase.pass ? "pass" : "fail"}</td>
11080
- <td>${escapeHtml19(testCase.status)}</td>
11437
+ <td>${escapeHtml20(testCase.status)}</td>
11081
11438
  <td>${String(testCase.attempts)}</td>
11082
11439
  <td>${String(testCase.elapsedMs)}ms</td>
11083
11440
  <td>${testCase.timedOut ? "yes" : "no"}</td>
11084
- <td>${escapeHtml19(testCase.issues.map((issue) => issue.message).join(" ") || testCase.error || "")}</td>
11441
+ <td>${escapeHtml20(testCase.issues.map((issue) => issue.message).join(" ") || testCase.error || "")}</td>
11085
11442
  </tr>`).join("");
11086
11443
  return `<section class="contract ${contract.pass ? "pass" : "fail"}">
11087
11444
  <div class="contract-header">
11088
11445
  <div>
11089
- <p class="eyebrow">${escapeHtml19(contract.toolName)}</p>
11090
- <h2>${escapeHtml19(contract.label ?? contract.contractId)}</h2>
11446
+ <p class="eyebrow">${escapeHtml20(contract.toolName)}</p>
11447
+ <h2>${escapeHtml20(contract.label ?? contract.contractId)}</h2>
11091
11448
  </div>
11092
11449
  <strong class="${contract.pass ? "pass" : "fail"}">${contract.pass ? "Passing" : "Failing"}</strong>
11093
11450
  </div>
@@ -11097,7 +11454,7 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
11097
11454
  </table>
11098
11455
  </section>`;
11099
11456
  }).join("");
11100
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml19(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.contract{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.14),rgba(245,158,11,.12))}.eyebrow{color:#fbbf24;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.contract-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}h2{margin:.2rem 0 1rem}.pass{color:#86efac}.fail{color:#fca5a5}.contract.fail{border-color:rgba(248,113,113,.45)}table{border-collapse:collapse;width:100%}td,th{border-bottom:1px solid #2a323a;padding:12px;text-align:left;vertical-align:top}th{color:#a8b0b8;font-size:.82rem}@media(max-width:800px){main{padding:18px}table{display:block;overflow:auto}.contract-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Tool Reliability</p><h1>${escapeHtml19(title)}</h1><div class="summary"><span class="pill ${report.status === "pass" ? "pass" : "fail"}">${escapeHtml19(report.status)}</span><span class="pill">${String(report.passed)} passing</span><span class="pill">${String(report.failed)} failing</span><span class="pill">${String(report.total)} contracts</span></div></section>${contracts || '<section class="contract"><p>No tool contracts configured.</p></section>'}</main></body></html>`;
11457
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml20(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.contract{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.14),rgba(245,158,11,.12))}.eyebrow{color:#fbbf24;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.contract-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}h2{margin:.2rem 0 1rem}.pass{color:#86efac}.fail{color:#fca5a5}.contract.fail{border-color:rgba(248,113,113,.45)}table{border-collapse:collapse;width:100%}td,th{border-bottom:1px solid #2a323a;padding:12px;text-align:left;vertical-align:top}th{color:#a8b0b8;font-size:.82rem}@media(max-width:800px){main{padding:18px}table{display:block;overflow:auto}.contract-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Tool Reliability</p><h1>${escapeHtml20(title)}</h1><div class="summary"><span class="pill ${report.status === "pass" ? "pass" : "fail"}">${escapeHtml20(report.status)}</span><span class="pill">${String(report.passed)} passing</span><span class="pill">${String(report.failed)} failing</span><span class="pill">${String(report.total)} contracts</span></div></section>${contracts || '<section class="contract"><p>No tool contracts configured.</p></section>'}</main></body></html>`;
11101
11458
  };
11102
11459
  var createVoiceToolContractJSONHandler = (options) => () => runVoiceToolContractSuite(options);
11103
11460
  var createVoiceToolContractHTMLHandler = (options) => async () => {
@@ -11114,7 +11471,7 @@ var createVoiceToolContractHTMLHandler = (options) => async () => {
11114
11471
  var createVoiceToolContractRoutes = (options) => {
11115
11472
  const path = options.path ?? "/api/tool-contracts";
11116
11473
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
11117
- const routes = new Elysia18({
11474
+ const routes = new Elysia19({
11118
11475
  name: options.name ?? "absolutejs-voice-tool-contracts"
11119
11476
  }).get(path, createVoiceToolContractJSONHandler(options));
11120
11477
  if (htmlPath) {
@@ -11124,7 +11481,7 @@ var createVoiceToolContractRoutes = (options) => {
11124
11481
  };
11125
11482
 
11126
11483
  // src/simulationSuite.ts
11127
- var escapeHtml20 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11484
+ var escapeHtml21 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11128
11485
  var summarizeSection = (report) => ({
11129
11486
  failed: report.failed,
11130
11487
  passed: report.passed,
@@ -11260,20 +11617,20 @@ var renderSection = (label, summary) => {
11260
11617
  if (!summary) {
11261
11618
  return "";
11262
11619
  }
11263
- return `<article class="${escapeHtml20(summary.status)}"><span>${escapeHtml20(label)}</span><strong>${escapeHtml20(summary.status)}</strong><p>${summary.passed}/${summary.total} passed, ${summary.failed} failed.</p></article>`;
11620
+ return `<article class="${escapeHtml21(summary.status)}"><span>${escapeHtml21(label)}</span><strong>${escapeHtml21(summary.status)}</strong><p>${summary.passed}/${summary.total} passed, ${summary.failed} failed.</p></article>`;
11264
11621
  };
11265
11622
  var renderAction = (action) => {
11266
- const content = `<strong>${escapeHtml20(action.label)}</strong><p>${escapeHtml20(action.description)}</p><span>${escapeHtml20(action.section)} / ${escapeHtml20(action.severity)}</span>`;
11267
- return action.href ? `<a class="action" href="${escapeHtml20(action.href)}">${content}</a>` : `<article class="action">${content}</article>`;
11623
+ const content = `<strong>${escapeHtml21(action.label)}</strong><p>${escapeHtml21(action.description)}</p><span>${escapeHtml21(action.section)} / ${escapeHtml21(action.severity)}</span>`;
11624
+ return action.href ? `<a class="action" href="${escapeHtml21(action.href)}">${content}</a>` : `<article class="action">${content}</article>`;
11268
11625
  };
11269
11626
  var renderVoiceSimulationSuiteHTML = (report, options = {}) => {
11270
11627
  const title = options.title ?? "Voice Simulation Suite";
11271
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml20(title)}</title><style>body{background:#10151c;color:#f8f3e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1080px;padding:32px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.18),rgba(59,130,246,.12));border:1px solid #283544;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#93c5fd;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.badge{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;padding:8px 12px}.pass{color:#86efac}.fail{color:#fca5a5}.grid,.actions{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));margin:18px 0}.grid article,.action{background:#151d27;border:1px solid #283544;border-radius:18px;color:inherit;padding:16px;text-decoration:none}.grid span,.action span{color:#aab5c0}.grid strong{display:block;font-size:2rem;margin:.25rem 0;text-transform:uppercase}.action strong{display:block;color:#f8f3e7;margin-bottom:.35rem}.action p{color:#d8dee6;margin:.3rem 0 .6rem}pre{background:#151d27;border:1px solid #283544;border-radius:18px;overflow:auto;padding:16px}</style></head><body><main><section class="hero"><p class="eyebrow">Pre-production proof</p><h1>${escapeHtml20(title)}</h1><p>One report for session quality, scenario evals, fixture simulations, tool contracts, and outcome contracts.</p><p class="badge ${escapeHtml20(report.status)}">Status: ${escapeHtml20(report.status)}</p><section class="grid">${renderSection("Sessions", report.summary.sessions)}${renderSection("Scenarios", report.summary.scenarios)}${renderSection("Fixtures", report.summary.fixtures)}${renderSection("Tools", report.summary.tools)}${renderSection("Outcomes", report.summary.outcomes)}</section></section><h2>Actions</h2><section class="actions">${report.actions.length > 0 ? report.actions.map(renderAction).join("") : '<article class="action"><strong>No action required</strong><p>All enabled simulation sections are passing.</p></article>'}</section><pre>${escapeHtml20(JSON.stringify({ summary: report.summary, actions: report.actions }, null, 2))}</pre></main></body></html>`;
11628
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml21(title)}</title><style>body{background:#10151c;color:#f8f3e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1080px;padding:32px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.18),rgba(59,130,246,.12));border:1px solid #283544;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#93c5fd;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.badge{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;padding:8px 12px}.pass{color:#86efac}.fail{color:#fca5a5}.grid,.actions{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));margin:18px 0}.grid article,.action{background:#151d27;border:1px solid #283544;border-radius:18px;color:inherit;padding:16px;text-decoration:none}.grid span,.action span{color:#aab5c0}.grid strong{display:block;font-size:2rem;margin:.25rem 0;text-transform:uppercase}.action strong{display:block;color:#f8f3e7;margin-bottom:.35rem}.action p{color:#d8dee6;margin:.3rem 0 .6rem}pre{background:#151d27;border:1px solid #283544;border-radius:18px;overflow:auto;padding:16px}</style></head><body><main><section class="hero"><p class="eyebrow">Pre-production proof</p><h1>${escapeHtml21(title)}</h1><p>One report for session quality, scenario evals, fixture simulations, tool contracts, and outcome contracts.</p><p class="badge ${escapeHtml21(report.status)}">Status: ${escapeHtml21(report.status)}</p><section class="grid">${renderSection("Sessions", report.summary.sessions)}${renderSection("Scenarios", report.summary.scenarios)}${renderSection("Fixtures", report.summary.fixtures)}${renderSection("Tools", report.summary.tools)}${renderSection("Outcomes", report.summary.outcomes)}</section></section><h2>Actions</h2><section class="actions">${report.actions.length > 0 ? report.actions.map(renderAction).join("") : '<article class="action"><strong>No action required</strong><p>All enabled simulation sections are passing.</p></article>'}</section><pre>${escapeHtml21(JSON.stringify({ summary: report.summary, actions: report.actions }, null, 2))}</pre></main></body></html>`;
11272
11629
  };
11273
11630
  var createVoiceSimulationSuiteRoutes = (options) => {
11274
11631
  const path = options.path ?? "/api/voice/simulations";
11275
11632
  const htmlPath = options.htmlPath === undefined ? "/voice/simulations" : options.htmlPath;
11276
- const app = new Elysia19({
11633
+ const app = new Elysia20({
11277
11634
  name: options.name ?? "absolutejs-voice-simulation-suite"
11278
11635
  }).get(path, () => runVoiceSimulationSuite(options));
11279
11636
  if (htmlPath) {
@@ -11585,10 +11942,10 @@ var createVoiceWorkflowContractHandler = (input) => {
11585
11942
  };
11586
11943
  };
11587
11944
  // src/turnLatency.ts
11588
- import { Elysia as Elysia20 } from "elysia";
11945
+ import { Elysia as Elysia21 } from "elysia";
11589
11946
  var DEFAULT_WARN_AFTER_MS = 1800;
11590
11947
  var DEFAULT_FAIL_AFTER_MS = 3200;
11591
- var escapeHtml21 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11948
+ var escapeHtml22 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11592
11949
  var firstNumber2 = (values) => values.filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
11593
11950
  var getString10 = (value) => typeof value === "string" && value.trim() ? value : undefined;
11594
11951
  var createTraceStageIndex = (events) => {
@@ -11702,11 +12059,11 @@ var summarizeVoiceTurnLatency = async (options) => {
11702
12059
  var formatMs2 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
11703
12060
  var renderVoiceTurnLatencyHTML = (report, options = {}) => {
11704
12061
  const title = options.title ?? "Voice Turn Latency";
11705
- const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml21(turn.status)}">
11706
- <header><div><p class="eyebrow">${escapeHtml21(turn.sessionId)} \xB7 ${escapeHtml21(turn.turnId)}</p><h2>${escapeHtml21(turn.text || "Empty turn")}</h2></div><strong>${escapeHtml21(turn.status)}</strong></header>
11707
- <dl>${turn.stages.map((stage) => `<div><dt>${escapeHtml21(stage.label)}</dt><dd>${escapeHtml21(formatMs2(stage.valueMs))}</dd></div>`).join("")}</dl>
12062
+ const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml22(turn.status)}">
12063
+ <header><div><p class="eyebrow">${escapeHtml22(turn.sessionId)} \xB7 ${escapeHtml22(turn.turnId)}</p><h2>${escapeHtml22(turn.text || "Empty turn")}</h2></div><strong>${escapeHtml22(turn.status)}</strong></header>
12064
+ <dl>${turn.stages.map((stage) => `<div><dt>${escapeHtml22(stage.label)}</dt><dd>${escapeHtml22(formatMs2(stage.valueMs))}</dd></div>`).join("")}</dl>
11708
12065
  </article>`).join("");
11709
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml21(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.turn{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(94,234,212,.16),rgba(251,191,36,.1))}.eyebrow{color:#5eead4;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.turn header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.warn,.empty{color:#fde68a}.fail{color:#fca5a5}.turn.fail{border-color:rgba(248,113,113,.45)}dl{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}dt{color:#a8b0b8;font-size:.8rem}dd{font-weight:900;margin:0}@media(max-width:800px){main{padding:18px}.turn header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">End-to-end responsiveness</p><h1>${escapeHtml21(title)}</h1><div class="summary"><span class="pill ${escapeHtml21(report.status)}">${escapeHtml21(report.status)}</span><span class="pill">${String(report.total)} turns</span><span class="pill">avg ${escapeHtml21(formatMs2(report.averageTotalMs))}</span><span class="pill">${String(report.warnings)} warnings</span><span class="pill">${String(report.failed)} failed</span></div></section>${turns || '<section class="turn"><p>No committed turns found.</p></section>'}</main></body></html>`;
12066
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml22(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.turn{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(94,234,212,.16),rgba(251,191,36,.1))}.eyebrow{color:#5eead4;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.turn header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.warn,.empty{color:#fde68a}.fail{color:#fca5a5}.turn.fail{border-color:rgba(248,113,113,.45)}dl{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}dt{color:#a8b0b8;font-size:.8rem}dd{font-weight:900;margin:0}@media(max-width:800px){main{padding:18px}.turn header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">End-to-end responsiveness</p><h1>${escapeHtml22(title)}</h1><div class="summary"><span class="pill ${escapeHtml22(report.status)}">${escapeHtml22(report.status)}</span><span class="pill">${String(report.total)} turns</span><span class="pill">avg ${escapeHtml22(formatMs2(report.averageTotalMs))}</span><span class="pill">${String(report.warnings)} warnings</span><span class="pill">${String(report.failed)} failed</span></div></section>${turns || '<section class="turn"><p>No committed turns found.</p></section>'}</main></body></html>`;
11710
12067
  };
11711
12068
  var createVoiceTurnLatencyJSONHandler = (options) => async () => summarizeVoiceTurnLatency(options);
11712
12069
  var createVoiceTurnLatencyHTMLHandler = (options) => async () => {
@@ -11723,7 +12080,7 @@ var createVoiceTurnLatencyHTMLHandler = (options) => async () => {
11723
12080
  var createVoiceTurnLatencyRoutes = (options) => {
11724
12081
  const path = options.path ?? "/api/turn-latency";
11725
12082
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
11726
- const routes = new Elysia20({
12083
+ const routes = new Elysia21({
11727
12084
  name: options.name ?? "absolutejs-voice-turn-latency"
11728
12085
  }).get(path, createVoiceTurnLatencyJSONHandler(options));
11729
12086
  if (htmlPath) {
@@ -11732,8 +12089,8 @@ var createVoiceTurnLatencyRoutes = (options) => {
11732
12089
  return routes;
11733
12090
  };
11734
12091
  // src/liveLatency.ts
11735
- import { Elysia as Elysia21 } from "elysia";
11736
- var escapeHtml22 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
12092
+ import { Elysia as Elysia22 } from "elysia";
12093
+ var escapeHtml23 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11737
12094
  var percentile = (values, percentileValue) => {
11738
12095
  if (values.length === 0) {
11739
12096
  return;
@@ -11781,13 +12138,13 @@ var summarizeVoiceLiveLatency = async (options) => {
11781
12138
  var formatMs3 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
11782
12139
  var renderVoiceLiveLatencyHTML = (report, options = {}) => {
11783
12140
  const title = options.title ?? "Voice Live Latency";
11784
- const rows = report.recent.map((sample) => `<tr><td>${escapeHtml22(sample.sessionId)}</td><td>${escapeHtml22(formatMs3(sample.latencyMs))}</td><td>${escapeHtml22(sample.status ?? "unknown")}</td><td>${escapeHtml22(new Date(sample.at).toLocaleString())}</td></tr>`).join("");
11785
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml22(title)}</title><style>body{background:#0c0f14;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1060px;padding:32px}.hero{background:linear-gradient(135deg,rgba(94,234,212,.16),rgba(245,158,11,.1));border:1px solid #26313d;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;padding:8px 12px}.pass{color:#86efac}.warn,.empty{color:#fbbf24}.fail{color:#fca5a5}.metrics{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:18px 0}.metrics article,table{background:#141922;border:1px solid #26313d;border-radius:18px}.metrics article{padding:16px}.metrics span{color:#a8b0b8}.metrics strong{display:block;font-size:2rem;margin-top:.25rem}table{border-collapse:collapse;overflow:hidden;width:100%}td,th{border-bottom:1px solid #26313d;padding:12px;text-align:left}@media(max-width:760px){main{padding:20px}}</style></head><body><main><section class="hero"><p class="eyebrow">Browser proof</p><h1>${escapeHtml22(title)}</h1><p>Recent real browser speech-to-assistant response measurements from persisted <code>client.live_latency</code> traces.</p><p class="status ${escapeHtml22(report.status)}">Status: ${escapeHtml22(report.status)}</p><section class="metrics"><article><span>p50</span><strong>${escapeHtml22(formatMs3(report.p50LatencyMs))}</strong></article><article><span>p95</span><strong>${escapeHtml22(formatMs3(report.p95LatencyMs))}</strong></article><article><span>Average</span><strong>${escapeHtml22(formatMs3(report.averageLatencyMs))}</strong></article><article><span>Samples</span><strong>${String(report.total)}</strong></article></section></section><table><thead><tr><th>Session</th><th>Latency</th><th>Status</th><th>Measured</th></tr></thead><tbody>${rows || '<tr><td colspan="4">No live latency samples yet.</td></tr>'}</tbody></table></main></body></html>`;
12141
+ const rows = report.recent.map((sample) => `<tr><td>${escapeHtml23(sample.sessionId)}</td><td>${escapeHtml23(formatMs3(sample.latencyMs))}</td><td>${escapeHtml23(sample.status ?? "unknown")}</td><td>${escapeHtml23(new Date(sample.at).toLocaleString())}</td></tr>`).join("");
12142
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml23(title)}</title><style>body{background:#0c0f14;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1060px;padding:32px}.hero{background:linear-gradient(135deg,rgba(94,234,212,.16),rgba(245,158,11,.1));border:1px solid #26313d;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;padding:8px 12px}.pass{color:#86efac}.warn,.empty{color:#fbbf24}.fail{color:#fca5a5}.metrics{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:18px 0}.metrics article,table{background:#141922;border:1px solid #26313d;border-radius:18px}.metrics article{padding:16px}.metrics span{color:#a8b0b8}.metrics strong{display:block;font-size:2rem;margin-top:.25rem}table{border-collapse:collapse;overflow:hidden;width:100%}td,th{border-bottom:1px solid #26313d;padding:12px;text-align:left}@media(max-width:760px){main{padding:20px}}</style></head><body><main><section class="hero"><p class="eyebrow">Browser proof</p><h1>${escapeHtml23(title)}</h1><p>Recent real browser speech-to-assistant response measurements from persisted <code>client.live_latency</code> traces.</p><p class="status ${escapeHtml23(report.status)}">Status: ${escapeHtml23(report.status)}</p><section class="metrics"><article><span>p50</span><strong>${escapeHtml23(formatMs3(report.p50LatencyMs))}</strong></article><article><span>p95</span><strong>${escapeHtml23(formatMs3(report.p95LatencyMs))}</strong></article><article><span>Average</span><strong>${escapeHtml23(formatMs3(report.averageLatencyMs))}</strong></article><article><span>Samples</span><strong>${String(report.total)}</strong></article></section></section><table><thead><tr><th>Session</th><th>Latency</th><th>Status</th><th>Measured</th></tr></thead><tbody>${rows || '<tr><td colspan="4">No live latency samples yet.</td></tr>'}</tbody></table></main></body></html>`;
11786
12143
  };
11787
12144
  var createVoiceLiveLatencyRoutes = (options) => {
11788
12145
  const path = options.path ?? "/api/live-latency";
11789
12146
  const htmlPath = options.htmlPath === undefined ? "/live-latency" : options.htmlPath;
11790
- const routes = new Elysia21({
12147
+ const routes = new Elysia22({
11791
12148
  name: options.name ?? "absolutejs-voice-live-latency"
11792
12149
  }).get(path, () => summarizeVoiceLiveLatency(options));
11793
12150
  if (htmlPath) {
@@ -11804,9 +12161,9 @@ var createVoiceLiveLatencyRoutes = (options) => {
11804
12161
  return routes;
11805
12162
  };
11806
12163
  // src/turnQuality.ts
11807
- import { Elysia as Elysia22 } from "elysia";
12164
+ import { Elysia as Elysia23 } from "elysia";
11808
12165
  var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
11809
- var escapeHtml23 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
12166
+ var escapeHtml24 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11810
12167
  var getTurnLatencyMs = (turn) => {
11811
12168
  const firstTranscriptAt = turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
11812
12169
  if (firstTranscriptAt === undefined) {
@@ -11877,24 +12234,24 @@ var summarizeVoiceTurnQuality = async (options) => {
11877
12234
  };
11878
12235
  var renderVoiceTurnQualityHTML = (report, options = {}) => {
11879
12236
  const title = options.title ?? "Voice Turn Quality";
11880
- const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml23(turn.status)}">
12237
+ const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml24(turn.status)}">
11881
12238
  <div class="turn-header">
11882
12239
  <div>
11883
- <p class="eyebrow">${escapeHtml23(turn.sessionId)} \xB7 ${escapeHtml23(turn.turnId)}</p>
11884
- <h2>${escapeHtml23(turn.text || "Empty turn")}</h2>
12240
+ <p class="eyebrow">${escapeHtml24(turn.sessionId)} \xB7 ${escapeHtml24(turn.turnId)}</p>
12241
+ <h2>${escapeHtml24(turn.text || "Empty turn")}</h2>
11885
12242
  </div>
11886
- <strong>${escapeHtml23(turn.status)}</strong>
12243
+ <strong>${escapeHtml24(turn.status)}</strong>
11887
12244
  </div>
11888
12245
  <dl>
11889
- <div><dt>Source</dt><dd>${escapeHtml23(turn.source ?? "unknown")}</dd></div>
12246
+ <div><dt>Source</dt><dd>${escapeHtml24(turn.source ?? "unknown")}</dd></div>
11890
12247
  <div><dt>Confidence</dt><dd>${turn.averageConfidence === undefined ? "n/a" : `${Math.round(turn.averageConfidence * 100)}%`}</dd></div>
11891
- <div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml23(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
11892
- <div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${escapeHtml23(turn.correctionProvider)}` : ""}` : "none"}</dd></div>
12248
+ <div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml24(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
12249
+ <div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${escapeHtml24(turn.correctionProvider)}` : ""}` : "none"}</dd></div>
11893
12250
  <div><dt>Transcripts</dt><dd>${String(turn.selectedTranscriptCount)} selected \xB7 ${String(turn.finalTranscriptCount)} final \xB7 ${String(turn.partialTranscriptCount)} partial</dd></div>
11894
12251
  <div><dt>Cost</dt><dd>${turn.costUnits === undefined ? "n/a" : String(turn.costUnits)}</dd></div>
11895
12252
  </dl>
11896
12253
  </article>`).join("");
11897
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml23(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.turn{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(251,191,36,.16),rgba(34,197,94,.1))}.eyebrow{color:#fbbf24;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.turn-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.warn,.unknown{color:#fde68a}.fail{color:#fca5a5}.turn.fail{border-color:rgba(248,113,113,.45)}dl{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}dt{color:#a8b0b8;font-size:.8rem}dd{margin:0}@media(max-width:800px){main{padding:18px}.turn-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Realtime STT Debugging</p><h1>${escapeHtml23(title)}</h1><div class="summary"><span class="pill ${escapeHtml23(report.status)}">${escapeHtml23(report.status)}</span><span class="pill">${String(report.total)} turns</span><span class="pill">${String(report.warnings)} warnings</span><span class="pill">${String(report.failed)} failed</span><span class="pill">${String(report.sessions)} sessions</span></div></section>${turns || '<section class="turn"><p>No committed turns found.</p></section>'}</main></body></html>`;
12254
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml24(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.turn{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(251,191,36,.16),rgba(34,197,94,.1))}.eyebrow{color:#fbbf24;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.turn-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.warn,.unknown{color:#fde68a}.fail{color:#fca5a5}.turn.fail{border-color:rgba(248,113,113,.45)}dl{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}dt{color:#a8b0b8;font-size:.8rem}dd{margin:0}@media(max-width:800px){main{padding:18px}.turn-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Realtime STT Debugging</p><h1>${escapeHtml24(title)}</h1><div class="summary"><span class="pill ${escapeHtml24(report.status)}">${escapeHtml24(report.status)}</span><span class="pill">${String(report.total)} turns</span><span class="pill">${String(report.warnings)} warnings</span><span class="pill">${String(report.failed)} failed</span><span class="pill">${String(report.sessions)} sessions</span></div></section>${turns || '<section class="turn"><p>No committed turns found.</p></section>'}</main></body></html>`;
11898
12255
  };
11899
12256
  var createVoiceTurnQualityJSONHandler = (options) => async () => summarizeVoiceTurnQuality(options);
11900
12257
  var createVoiceTurnQualityHTMLHandler = (options) => async () => {
@@ -11911,7 +12268,7 @@ var createVoiceTurnQualityHTMLHandler = (options) => async () => {
11911
12268
  var createVoiceTurnQualityRoutes = (options) => {
11912
12269
  const path = options.path ?? "/api/turn-quality";
11913
12270
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
11914
- const routes = new Elysia22({
12271
+ const routes = new Elysia23({
11915
12272
  name: options.name ?? "absolutejs-voice-turn-quality"
11916
12273
  }).get(path, createVoiceTurnQualityJSONHandler(options));
11917
12274
  if (htmlPath) {
@@ -11920,7 +12277,7 @@ var createVoiceTurnQualityRoutes = (options) => {
11920
12277
  return routes;
11921
12278
  };
11922
12279
  // src/telephonyOutcome.ts
11923
- import { Elysia as Elysia23 } from "elysia";
12280
+ import { Elysia as Elysia24 } from "elysia";
11924
12281
  var DEFAULT_COMPLETED_STATUSES = [
11925
12282
  "answered",
11926
12283
  "completed",
@@ -12567,7 +12924,7 @@ var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
12567
12924
  var createVoiceTelephonyWebhookRoutes = (options = {}) => {
12568
12925
  const path = options.path ?? "/api/voice/telephony/webhook";
12569
12926
  const handler = createVoiceTelephonyWebhookHandler(options);
12570
- return new Elysia23({
12927
+ return new Elysia24({
12571
12928
  name: options.name ?? "absolutejs-voice-telephony-webhooks"
12572
12929
  }).post(path, async ({ query, request }) => {
12573
12930
  try {
@@ -12588,15 +12945,15 @@ var createVoiceTelephonyWebhookRoutes = (options = {}) => {
12588
12945
  });
12589
12946
  };
12590
12947
  // src/phoneAgent.ts
12591
- import { Elysia as Elysia27 } from "elysia";
12948
+ import { Elysia as Elysia28 } from "elysia";
12592
12949
 
12593
12950
  // src/telephony/plivo.ts
12594
12951
  import { Buffer as Buffer4 } from "buffer";
12595
- import { Elysia as Elysia25 } from "elysia";
12952
+ import { Elysia as Elysia26 } from "elysia";
12596
12953
 
12597
12954
  // src/telephony/twilio.ts
12598
12955
  import { Buffer as Buffer3 } from "buffer";
12599
- import { Elysia as Elysia24 } from "elysia";
12956
+ import { Elysia as Elysia25 } from "elysia";
12600
12957
  var TWILIO_MULAW_SAMPLE_RATE = 8000;
12601
12958
  var VOICE_PCM_SAMPLE_RATE = 16000;
12602
12959
  var escapeXml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
@@ -12626,7 +12983,7 @@ var resolveTwilioStreamParameters = async (parameters, input) => {
12626
12983
  return parameters;
12627
12984
  };
12628
12985
  var joinUrlPath = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
12629
- var escapeHtml24 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
12986
+ var escapeHtml25 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
12630
12987
  var getWebhookVerificationUrl = (webhook, input) => {
12631
12988
  if (!webhook?.verificationUrl) {
12632
12989
  return;
@@ -12669,23 +13026,23 @@ var buildTwilioVoiceSetupStatus = async (options, input) => {
12669
13026
  };
12670
13027
  var renderTwilioVoiceSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
12671
13028
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio setup</p>
12672
- <h1>${escapeHtml24(title)}</h1>
13029
+ <h1>${escapeHtml25(title)}</h1>
12673
13030
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
12674
13031
  <section>
12675
13032
  <h2>URLs</h2>
12676
13033
  <ul>
12677
- <li><strong>TwiML:</strong> <code>${escapeHtml24(status.urls.twiml)}</code></li>
12678
- <li><strong>Media stream:</strong> <code>${escapeHtml24(status.urls.stream)}</code></li>
12679
- <li><strong>Status webhook:</strong> <code>${escapeHtml24(status.urls.webhook)}</code></li>
13034
+ <li><strong>TwiML:</strong> <code>${escapeHtml25(status.urls.twiml)}</code></li>
13035
+ <li><strong>Media stream:</strong> <code>${escapeHtml25(status.urls.stream)}</code></li>
13036
+ <li><strong>Status webhook:</strong> <code>${escapeHtml25(status.urls.webhook)}</code></li>
12680
13037
  </ul>
12681
13038
  </section>
12682
13039
  <section>
12683
13040
  <h2>Signing</h2>
12684
13041
  <p>Mode: <code>${status.signing.mode}</code></p>
12685
- ${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml24(status.signing.verificationUrl)}</code></p>` : ""}
13042
+ ${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml25(status.signing.verificationUrl)}</code></p>` : ""}
12686
13043
  </section>
12687
- ${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml24(name)}</code></li>`).join("")}</ul></section>` : ""}
12688
- ${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml24(warning)}</li>`).join("")}</ul></section>` : ""}
13044
+ ${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml25(name)}</code></li>`).join("")}</ul></section>` : ""}
13045
+ ${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml25(warning)}</li>`).join("")}</ul></section>` : ""}
12689
13046
  </main>`;
12690
13047
  var extractTwilioStreamUrl = (twiml) => twiml.match(/<Stream\b[^>]*\surl="([^"]+)"/i)?.[1]?.replaceAll("&amp;", "&");
12691
13048
  var createSmokeCheck = (name, status, message, details) => ({
@@ -12696,20 +13053,20 @@ var createSmokeCheck = (name, status, message, details) => ({
12696
13053
  });
12697
13054
  var renderTwilioVoiceSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
12698
13055
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio smoke test</p>
12699
- <h1>${escapeHtml24(title)}</h1>
13056
+ <h1>${escapeHtml25(title)}</h1>
12700
13057
  <p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
12701
13058
  <section>
12702
13059
  <h2>Checks</h2>
12703
13060
  <ul>
12704
- ${report.checks.map((check) => `<li><strong>${escapeHtml24(check.name)}</strong>: ${escapeHtml24(check.status)}${check.message ? ` - ${escapeHtml24(check.message)}` : ""}</li>`).join("")}
13061
+ ${report.checks.map((check) => `<li><strong>${escapeHtml25(check.name)}</strong>: ${escapeHtml25(check.status)}${check.message ? ` - ${escapeHtml25(check.message)}` : ""}</li>`).join("")}
12705
13062
  </ul>
12706
13063
  </section>
12707
13064
  <section>
12708
13065
  <h2>Observed URLs</h2>
12709
13066
  <ul>
12710
- <li><strong>TwiML:</strong> <code>${escapeHtml24(report.setup.urls.twiml)}</code></li>
12711
- <li><strong>Stream:</strong> <code>${escapeHtml24(report.twiml?.streamUrl ?? report.setup.urls.stream)}</code></li>
12712
- <li><strong>Webhook:</strong> <code>${escapeHtml24(report.setup.urls.webhook)}</code></li>
13067
+ <li><strong>TwiML:</strong> <code>${escapeHtml25(report.setup.urls.twiml)}</code></li>
13068
+ <li><strong>Stream:</strong> <code>${escapeHtml25(report.twiml?.streamUrl ?? report.setup.urls.stream)}</code></li>
13069
+ <li><strong>Webhook:</strong> <code>${escapeHtml25(report.setup.urls.webhook)}</code></li>
12713
13070
  </ul>
12714
13071
  </section>
12715
13072
  </main>`;
@@ -13169,7 +13526,7 @@ var createTwilioVoiceRoutes = (options) => {
13169
13526
  const smokePath = options.smoke?.path === false ? false : options.smoke?.path ?? "/api/voice/twilio/smoke";
13170
13527
  const bridges = new WeakMap;
13171
13528
  const webhookPolicy = options.webhook?.policy ?? options.outcomePolicy ?? createVoiceTelephonyOutcomePolicy();
13172
- const app = new Elysia24({
13529
+ const app = new Elysia25({
13173
13530
  name: options.name ?? "absolutejs-voice-twilio"
13174
13531
  }).get(twimlPath, async ({ query, request }) => {
13175
13532
  const streamUrl = await resolveTwilioStreamUrl(options, {
@@ -13306,7 +13663,7 @@ var createTwilioVoiceRoutes = (options) => {
13306
13663
 
13307
13664
  // src/telephony/plivo.ts
13308
13665
  var escapeXml3 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
13309
- var escapeHtml25 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
13666
+ var escapeHtml26 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
13310
13667
  var joinUrlPath2 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
13311
13668
  var resolveRequestOrigin2 = (request) => {
13312
13669
  const url = new URL(request.url);
@@ -13557,21 +13914,21 @@ var buildPlivoVoiceSetupStatus = async (options, input) => {
13557
13914
  };
13558
13915
  var renderPlivoSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
13559
13916
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo setup</p>
13560
- <h1>${escapeHtml25(title)}</h1>
13917
+ <h1>${escapeHtml26(title)}</h1>
13561
13918
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
13562
13919
  <ul>
13563
- <li><strong>Answer XML:</strong> <code>${escapeHtml25(status.urls.answer)}</code></li>
13564
- <li><strong>Audio stream:</strong> <code>${escapeHtml25(status.urls.stream)}</code></li>
13565
- <li><strong>Status webhook:</strong> <code>${escapeHtml25(status.urls.webhook)}</code></li>
13920
+ <li><strong>Answer XML:</strong> <code>${escapeHtml26(status.urls.answer)}</code></li>
13921
+ <li><strong>Audio stream:</strong> <code>${escapeHtml26(status.urls.stream)}</code></li>
13922
+ <li><strong>Status webhook:</strong> <code>${escapeHtml26(status.urls.webhook)}</code></li>
13566
13923
  </ul>
13567
- ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml25(name)}</code></li>`).join("")}</ul>` : ""}
13568
- ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml25(warning)}</li>`).join("")}</ul>` : ""}
13924
+ ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml26(name)}</code></li>`).join("")}</ul>` : ""}
13925
+ ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml26(warning)}</li>`).join("")}</ul>` : ""}
13569
13926
  </main>`;
13570
13927
  var renderPlivoSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
13571
13928
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo smoke test</p>
13572
- <h1>${escapeHtml25(title)}</h1>
13929
+ <h1>${escapeHtml26(title)}</h1>
13573
13930
  <p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
13574
- <ul>${report.checks.map((check) => `<li><strong>${escapeHtml25(check.name)}</strong>: ${escapeHtml25(check.status)}${check.message ? ` - ${escapeHtml25(check.message)}` : ""}</li>`).join("")}</ul>
13931
+ <ul>${report.checks.map((check) => `<li><strong>${escapeHtml26(check.name)}</strong>: ${escapeHtml26(check.status)}${check.message ? ` - ${escapeHtml26(check.message)}` : ""}</li>`).join("")}</ul>
13575
13932
  </main>`;
13576
13933
  var runPlivoSmokeTest = async (input) => {
13577
13934
  const setup = await buildPlivoVoiceSetupStatus(input.options, input);
@@ -13666,7 +14023,7 @@ var createPlivoVoiceRoutes = (options = {}) => {
13666
14023
  request: input.request
13667
14024
  }) : verificationUrl ?? input.request.url
13668
14025
  }) : undefined);
13669
- const app = new Elysia25({
14026
+ const app = new Elysia26({
13670
14027
  name: options.name ?? "absolutejs-voice-plivo"
13671
14028
  }).get(answerPath, async ({ query, request }) => {
13672
14029
  const streamUrl = await resolvePlivoStreamUrl(options, {
@@ -13777,9 +14134,9 @@ var createPlivoVoiceRoutes = (options = {}) => {
13777
14134
 
13778
14135
  // src/telephony/telnyx.ts
13779
14136
  import { Buffer as Buffer5 } from "buffer";
13780
- import { Elysia as Elysia26 } from "elysia";
14137
+ import { Elysia as Elysia27 } from "elysia";
13781
14138
  var escapeXml4 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
13782
- var escapeHtml26 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
14139
+ var escapeHtml27 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
13783
14140
  var joinUrlPath3 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
13784
14141
  var resolveRequestOrigin3 = (request) => {
13785
14142
  const url = new URL(request.url);
@@ -13980,21 +14337,21 @@ var buildTelnyxVoiceSetupStatus = async (options, input) => {
13980
14337
  };
13981
14338
  var renderTelnyxSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
13982
14339
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx setup</p>
13983
- <h1>${escapeHtml26(title)}</h1>
14340
+ <h1>${escapeHtml27(title)}</h1>
13984
14341
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
13985
14342
  <ul>
13986
- <li><strong>TeXML:</strong> <code>${escapeHtml26(status.urls.texml)}</code></li>
13987
- <li><strong>Media stream:</strong> <code>${escapeHtml26(status.urls.stream)}</code></li>
13988
- <li><strong>Status webhook:</strong> <code>${escapeHtml26(status.urls.webhook)}</code></li>
14343
+ <li><strong>TeXML:</strong> <code>${escapeHtml27(status.urls.texml)}</code></li>
14344
+ <li><strong>Media stream:</strong> <code>${escapeHtml27(status.urls.stream)}</code></li>
14345
+ <li><strong>Status webhook:</strong> <code>${escapeHtml27(status.urls.webhook)}</code></li>
13989
14346
  </ul>
13990
- ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml26(name)}</code></li>`).join("")}</ul>` : ""}
13991
- ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml26(warning)}</li>`).join("")}</ul>` : ""}
14347
+ ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml27(name)}</code></li>`).join("")}</ul>` : ""}
14348
+ ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml27(warning)}</li>`).join("")}</ul>` : ""}
13992
14349
  </main>`;
13993
14350
  var renderTelnyxSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
13994
14351
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx smoke test</p>
13995
- <h1>${escapeHtml26(title)}</h1>
14352
+ <h1>${escapeHtml27(title)}</h1>
13996
14353
  <p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
13997
- <ul>${report.checks.map((check) => `<li><strong>${escapeHtml26(check.name)}</strong>: ${escapeHtml26(check.status)}${check.message ? ` - ${escapeHtml26(check.message)}` : ""}</li>`).join("")}</ul>
14354
+ <ul>${report.checks.map((check) => `<li><strong>${escapeHtml27(check.name)}</strong>: ${escapeHtml27(check.status)}${check.message ? ` - ${escapeHtml27(check.message)}` : ""}</li>`).join("")}</ul>
13998
14355
  </main>`;
13999
14356
  var runTelnyxSmokeTest = async (input) => {
14000
14357
  const setup = await buildTelnyxVoiceSetupStatus(input.options, input);
@@ -14088,7 +14445,7 @@ var createTelnyxVoiceRoutes = (options = {}) => {
14088
14445
  publicKey: options.webhook?.publicKey,
14089
14446
  toleranceSeconds: options.webhook?.toleranceSeconds
14090
14447
  }) : undefined);
14091
- const app = new Elysia26({
14448
+ const app = new Elysia27({
14092
14449
  name: options.name ?? "absolutejs-voice-telnyx"
14093
14450
  }).get(texmlPath, async ({ query, request }) => {
14094
14451
  const streamUrl = await resolveTelnyxStreamUrl(options, {
@@ -14258,7 +14615,7 @@ var createVoicePhoneAgent = (options) => {
14258
14615
  setupPath: resolveSetupPath(carrier),
14259
14616
  smokePath: resolveSmokePath(carrier)
14260
14617
  }));
14261
- const app = new Elysia27({
14618
+ const app = new Elysia28({
14262
14619
  name: options.name ?? "absolutejs-voice-phone-agent"
14263
14620
  });
14264
14621
  for (const carrier of options.carriers) {
@@ -16539,7 +16896,7 @@ var createVoiceMemoryStore = () => {
16539
16896
  return { get, getOrCreate, list, remove, set };
16540
16897
  };
16541
16898
  // src/opsWebhook.ts
16542
- import { Elysia as Elysia28 } from "elysia";
16899
+ import { Elysia as Elysia29 } from "elysia";
16543
16900
  var toHex5 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
16544
16901
  var signVoiceOpsWebhookBody = async (input) => {
16545
16902
  const encoder = new TextEncoder;
@@ -16669,7 +17026,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
16669
17026
  };
16670
17027
  var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
16671
17028
  const path = options.path ?? "/api/voice-ops/webhook";
16672
- return new Elysia28().post(path, async ({ body, request, set }) => {
17029
+ return new Elysia29().post(path, async ({ body, request, set }) => {
16673
17030
  const bodyText = typeof body === "string" ? body : JSON.stringify(body);
16674
17031
  if (options.signingSecret) {
16675
17032
  const verification = await verifyVoiceOpsWebhookSignature({
@@ -18478,6 +18835,7 @@ export {
18478
18835
  summarizeVoiceIntegrationEvents,
18479
18836
  summarizeVoiceHandoffHealth,
18480
18837
  summarizeVoiceHandoffDeliveries,
18838
+ summarizeVoiceCampaigns,
18481
18839
  summarizeVoiceBargeIn,
18482
18840
  summarizeVoiceAssistantRuns,
18483
18841
  summarizeVoiceAssistantHealth,
@@ -18494,6 +18852,7 @@ export {
18494
18852
  runVoiceScenarioFixtureEvals,
18495
18853
  runVoiceScenarioEvals,
18496
18854
  runVoiceOutcomeContractSuite,
18855
+ runVoiceCampaignProof,
18497
18856
  resolveVoiceTraceRedactionOptions,
18498
18857
  resolveVoiceTelephonyOutcome,
18499
18858
  resolveVoiceSTTRoutingStrategy,
@@ -18533,6 +18892,7 @@ export {
18533
18892
  renderVoiceHandoffHealthHTML,
18534
18893
  renderVoiceEvalHTML,
18535
18894
  renderVoiceEvalBaselineHTML,
18895
+ renderVoiceCampaignsHTML,
18536
18896
  renderVoiceCallReviewMarkdown,
18537
18897
  renderVoiceCallReviewHTML,
18538
18898
  renderVoiceBargeInHTML,
@@ -18668,6 +19028,7 @@ export {
18668
19028
  createVoiceMemoryTraceEventStore,
18669
19029
  createVoiceMemoryStore,
18670
19030
  createVoiceMemoryHandoffDeliveryStore,
19031
+ createVoiceMemoryCampaignStore,
18671
19032
  createVoiceMemoryAssistantMemoryStore,
18672
19033
  createVoiceLiveLatencyRoutes,
18673
19034
  createVoiceLinearIssueUpdateSink,
@@ -18703,6 +19064,8 @@ export {
18703
19064
  createVoiceExperiment,
18704
19065
  createVoiceEvalRoutes,
18705
19066
  createVoiceDiagnosticsRoutes,
19067
+ createVoiceCampaignRoutes,
19068
+ createVoiceCampaign,
18706
19069
  createVoiceCallReviewRecorder,
18707
19070
  createVoiceCallReviewFromSession,
18708
19071
  createVoiceCallReviewFromLiveTelephonyReport,