@absolutejs/voice 0.0.22-beta.106 → 0.0.22-beta.108

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,301 @@ 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 escapeHtml18 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10798
+ var renderVoiceCampaignsHTML = (records, options = {}) => {
10799
+ const title = options.title ?? "Voice Campaigns";
10800
+ 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("");
10801
+ const summary = summarizeVoiceCampaigns(records);
10802
+ 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>`;
10803
+ };
10804
+ var readJsonBody = async (request) => {
10805
+ const text = await request.text();
10806
+ return text.trim() ? JSON.parse(text) : {};
10807
+ };
10808
+ var createVoiceCampaignRoutes = (options) => {
10809
+ const runtime = createVoiceCampaign(options);
10810
+ const path = options.path ?? "/api/voice/campaigns";
10811
+ const htmlPath = options.htmlPath === undefined ? "/voice/campaigns" : options.htmlPath;
10812
+ const app = new Elysia17({ name: options.name ?? "absolutejs-voice-campaigns" }).get(path, async () => ({
10813
+ campaigns: await runtime.list(),
10814
+ summary: await runtime.summarize()
10815
+ })).post(path, async ({ request }) => runtime.create(await readJsonBody(request))).get(`${path}/:campaignId`, ({ params }) => runtime.get(params.campaignId)).delete(`${path}/:campaignId`, async ({ params }) => {
10816
+ await runtime.remove(params.campaignId);
10817
+ return { ok: true };
10818
+ }).post(`${path}/:campaignId/recipients`, async ({ params, request }) => {
10819
+ const body = await readJsonBody(request);
10820
+ return runtime.addRecipients(params.campaignId, body.recipients ?? []);
10821
+ }).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 ({
10822
+ params,
10823
+ request
10824
+ }) => runtime.completeAttempt(params.campaignId, params.attemptId, await readJsonBody(request)));
10825
+ if (htmlPath) {
10826
+ app.get(htmlPath, async () => {
10827
+ const records = await runtime.list();
10828
+ return new Response(renderVoiceCampaignsHTML(records, options), {
10829
+ headers: {
10830
+ "content-type": "text/html; charset=utf-8",
10831
+ ...options.headers
10832
+ }
10833
+ });
10834
+ });
10835
+ }
10836
+ return app;
10837
+ };
10549
10838
  // src/simulationSuite.ts
10550
- import { Elysia as Elysia19 } from "elysia";
10839
+ import { Elysia as Elysia20 } from "elysia";
10551
10840
 
10552
10841
  // 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;");
10842
+ import { Elysia as Elysia18 } from "elysia";
10843
+ var escapeHtml19 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10555
10844
  var getPayloadString = (event, key) => typeof event.payload[key] === "string" ? event.payload[key] : undefined;
10556
10845
  var toList = async (input) => Array.isArray(input) ? input : await input?.list() ?? [];
10557
10846
  var hydrateSessions = async (input) => {
@@ -10659,9 +10948,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
10659
10948
  const contracts = report.contracts.map((contract) => `<section class="contract ${contract.pass ? "pass" : "fail"}">
10660
10949
  <div class="contract-header">
10661
10950
  <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>` : ""}
10951
+ <p class="eyebrow">${escapeHtml19(contract.contractId)}</p>
10952
+ <h2>${escapeHtml19(contract.label ?? contract.contractId)}</h2>
10953
+ ${contract.description ? `<p>${escapeHtml19(contract.description)}</p>` : ""}
10665
10954
  </div>
10666
10955
  <strong>${contract.pass ? "pass" : "fail"}</strong>
10667
10956
  </div>
@@ -10672,9 +10961,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
10672
10961
  <span>handoffs ${String(contract.matched.handoffs)}</span>
10673
10962
  <span>events ${String(contract.matched.integrationEvents)}</span>
10674
10963
  </div>
10675
- ${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml18(issue.message)}</li>`).join("")}</ul>` : ""}
10964
+ ${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml19(issue.message)}</li>`).join("")}</ul>` : ""}
10676
10965
  </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>`;
10966
+ 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
10967
  };
10679
10968
  var createVoiceOutcomeContractJSONHandler = (options) => async () => runVoiceOutcomeContractSuite(options);
10680
10969
  var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
@@ -10690,7 +10979,7 @@ var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
10690
10979
  var createVoiceOutcomeContractRoutes = (options) => {
10691
10980
  const path = options.path ?? "/api/outcome-contracts";
10692
10981
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
10693
- const routes = new Elysia17({
10982
+ const routes = new Elysia18({
10694
10983
  name: options.name ?? "absolutejs-voice-outcome-contracts"
10695
10984
  }).get(path, createVoiceOutcomeContractJSONHandler(options));
10696
10985
  if (htmlPath) {
@@ -10700,7 +10989,7 @@ var createVoiceOutcomeContractRoutes = (options) => {
10700
10989
  };
10701
10990
 
10702
10991
  // src/toolContract.ts
10703
- import { Elysia as Elysia18 } from "elysia";
10992
+ import { Elysia as Elysia19 } from "elysia";
10704
10993
 
10705
10994
  // src/toolRuntime.ts
10706
10995
  var toErrorMessage4 = (error) => error instanceof Error ? error.message : String(error);
@@ -10909,7 +11198,7 @@ var createDefaultTurn = (caseId) => ({
10909
11198
  });
10910
11199
  var defaultApi = {};
10911
11200
  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;");
11201
+ var escapeHtml20 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10913
11202
  var evaluateExpectation = (input) => {
10914
11203
  const issues = [];
10915
11204
  const expect = input.expect;
@@ -11075,19 +11364,19 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
11075
11364
  const title = options.title ?? "Voice Tool Contracts";
11076
11365
  const contracts = report.contracts.map((contract) => {
11077
11366
  const cases = contract.cases.map((testCase) => `<tr>
11078
- <td>${escapeHtml19(testCase.label ?? testCase.caseId)}</td>
11367
+ <td>${escapeHtml20(testCase.label ?? testCase.caseId)}</td>
11079
11368
  <td class="${testCase.pass ? "pass" : "fail"}">${testCase.pass ? "pass" : "fail"}</td>
11080
- <td>${escapeHtml19(testCase.status)}</td>
11369
+ <td>${escapeHtml20(testCase.status)}</td>
11081
11370
  <td>${String(testCase.attempts)}</td>
11082
11371
  <td>${String(testCase.elapsedMs)}ms</td>
11083
11372
  <td>${testCase.timedOut ? "yes" : "no"}</td>
11084
- <td>${escapeHtml19(testCase.issues.map((issue) => issue.message).join(" ") || testCase.error || "")}</td>
11373
+ <td>${escapeHtml20(testCase.issues.map((issue) => issue.message).join(" ") || testCase.error || "")}</td>
11085
11374
  </tr>`).join("");
11086
11375
  return `<section class="contract ${contract.pass ? "pass" : "fail"}">
11087
11376
  <div class="contract-header">
11088
11377
  <div>
11089
- <p class="eyebrow">${escapeHtml19(contract.toolName)}</p>
11090
- <h2>${escapeHtml19(contract.label ?? contract.contractId)}</h2>
11378
+ <p class="eyebrow">${escapeHtml20(contract.toolName)}</p>
11379
+ <h2>${escapeHtml20(contract.label ?? contract.contractId)}</h2>
11091
11380
  </div>
11092
11381
  <strong class="${contract.pass ? "pass" : "fail"}">${contract.pass ? "Passing" : "Failing"}</strong>
11093
11382
  </div>
@@ -11097,7 +11386,7 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
11097
11386
  </table>
11098
11387
  </section>`;
11099
11388
  }).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>`;
11389
+ 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
11390
  };
11102
11391
  var createVoiceToolContractJSONHandler = (options) => () => runVoiceToolContractSuite(options);
11103
11392
  var createVoiceToolContractHTMLHandler = (options) => async () => {
@@ -11114,7 +11403,7 @@ var createVoiceToolContractHTMLHandler = (options) => async () => {
11114
11403
  var createVoiceToolContractRoutes = (options) => {
11115
11404
  const path = options.path ?? "/api/tool-contracts";
11116
11405
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
11117
- const routes = new Elysia18({
11406
+ const routes = new Elysia19({
11118
11407
  name: options.name ?? "absolutejs-voice-tool-contracts"
11119
11408
  }).get(path, createVoiceToolContractJSONHandler(options));
11120
11409
  if (htmlPath) {
@@ -11124,7 +11413,7 @@ var createVoiceToolContractRoutes = (options) => {
11124
11413
  };
11125
11414
 
11126
11415
  // src/simulationSuite.ts
11127
- var escapeHtml20 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11416
+ var escapeHtml21 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11128
11417
  var summarizeSection = (report) => ({
11129
11418
  failed: report.failed,
11130
11419
  passed: report.passed,
@@ -11132,6 +11421,71 @@ var summarizeSection = (report) => ({
11132
11421
  total: report.total
11133
11422
  });
11134
11423
  var hasWork = (options) => Boolean(options.store || options.scenarios?.length || options.fixtures?.length || options.fixtureStore || options.tools?.length || options.outcomes?.contracts.length);
11424
+ var collectSimulationActions = (input) => {
11425
+ const actions = [];
11426
+ if (input.sessions?.failed) {
11427
+ const firstFailed = input.sessions.sessions.find((session) => session.status === "fail");
11428
+ actions.push({
11429
+ description: firstFailed ? `Inspect session ${firstFailed.sessionId}; at least one quality metric is outside threshold.` : "Inspect failing session quality reports.",
11430
+ href: input.links?.sessions,
11431
+ label: "Review failing session quality",
11432
+ section: "sessions",
11433
+ severity: "error"
11434
+ });
11435
+ }
11436
+ for (const scenario of input.scenarios?.scenarios ?? []) {
11437
+ if (scenario.status !== "fail") {
11438
+ continue;
11439
+ }
11440
+ const issue = scenario.issues[0] ?? scenario.sessions.find((session) => session.issues.length > 0)?.issues[0] ?? "Scenario did not meet its expected trace conditions.";
11441
+ actions.push({
11442
+ description: `${scenario.label}: ${issue}`,
11443
+ href: input.links?.scenarios,
11444
+ label: `Fix scenario ${scenario.label}`,
11445
+ section: "scenarios",
11446
+ severity: "error"
11447
+ });
11448
+ }
11449
+ for (const fixture of input.fixtures?.fixtures ?? []) {
11450
+ if (fixture.status !== "fail") {
11451
+ continue;
11452
+ }
11453
+ const failedScenario = fixture.report.scenarios.find((scenario) => scenario.status === "fail");
11454
+ actions.push({
11455
+ description: failedScenario ? `${fixture.label}: ${failedScenario.label} failed.` : `${fixture.label}: fixture simulation failed.`,
11456
+ href: input.links?.fixtures,
11457
+ label: `Update fixture ${fixture.label}`,
11458
+ section: "fixtures",
11459
+ severity: "error"
11460
+ });
11461
+ }
11462
+ for (const tool of input.tools?.contracts ?? []) {
11463
+ if (tool.pass) {
11464
+ continue;
11465
+ }
11466
+ const issue = tool.issues[0] ?? tool.cases.find((testCase) => !testCase.pass)?.issues[0];
11467
+ actions.push({
11468
+ description: issue?.message ?? `${tool.toolName} contract failed.`,
11469
+ href: input.links?.tools,
11470
+ label: `Fix tool contract ${tool.label ?? tool.contractId}`,
11471
+ section: "tools",
11472
+ severity: "error"
11473
+ });
11474
+ }
11475
+ for (const outcome of input.outcomes?.contracts ?? []) {
11476
+ if (outcome.pass) {
11477
+ continue;
11478
+ }
11479
+ actions.push({
11480
+ description: outcome.issues[0]?.message ?? `${outcome.label ?? outcome.contractId} is missing required outcome evidence.`,
11481
+ href: input.links?.outcomes,
11482
+ label: `Fix outcome ${outcome.label ?? outcome.contractId}`,
11483
+ section: "outcomes",
11484
+ severity: "error"
11485
+ });
11486
+ }
11487
+ return actions;
11488
+ };
11135
11489
  var runVoiceSimulationSuite = async (options) => {
11136
11490
  const include = options.include ?? {};
11137
11491
  const shouldRunSessions = include.sessions ?? Boolean(options.store || !hasWork(options));
@@ -11162,7 +11516,16 @@ var runVoiceSimulationSuite = async (options) => {
11162
11516
  const sections = [sessions, scenarios, fixtures, tools, outcomes].filter((report) => Boolean(report));
11163
11517
  const failed = sections.filter((section) => section.status === "fail").length;
11164
11518
  const passed = sections.length - failed;
11519
+ const actions = collectSimulationActions({
11520
+ fixtures,
11521
+ links: options.actionLinks,
11522
+ outcomes,
11523
+ scenarios,
11524
+ sessions,
11525
+ tools
11526
+ });
11165
11527
  return {
11528
+ actions,
11166
11529
  checkedAt: Date.now(),
11167
11530
  failed,
11168
11531
  fixtures,
@@ -11186,16 +11549,20 @@ var renderSection = (label, summary) => {
11186
11549
  if (!summary) {
11187
11550
  return "";
11188
11551
  }
11189
- 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>`;
11552
+ 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>`;
11553
+ };
11554
+ var renderAction = (action) => {
11555
+ const content = `<strong>${escapeHtml21(action.label)}</strong><p>${escapeHtml21(action.description)}</p><span>${escapeHtml21(action.section)} / ${escapeHtml21(action.severity)}</span>`;
11556
+ return action.href ? `<a class="action" href="${escapeHtml21(action.href)}">${content}</a>` : `<article class="action">${content}</article>`;
11190
11557
  };
11191
11558
  var renderVoiceSimulationSuiteHTML = (report, options = {}) => {
11192
11559
  const title = options.title ?? "Voice Simulation Suite";
11193
- 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{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));margin:18px 0}.grid article{background:#151d27;border:1px solid #283544;border-radius:18px;padding:16px}.grid span{color:#aab5c0}.grid strong{display:block;font-size:2rem;margin:.25rem 0;text-transform:uppercase}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><pre>${escapeHtml20(JSON.stringify(report.summary, null, 2))}</pre></main></body></html>`;
11560
+ 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>`;
11194
11561
  };
11195
11562
  var createVoiceSimulationSuiteRoutes = (options) => {
11196
11563
  const path = options.path ?? "/api/voice/simulations";
11197
11564
  const htmlPath = options.htmlPath === undefined ? "/voice/simulations" : options.htmlPath;
11198
- const app = new Elysia19({
11565
+ const app = new Elysia20({
11199
11566
  name: options.name ?? "absolutejs-voice-simulation-suite"
11200
11567
  }).get(path, () => runVoiceSimulationSuite(options));
11201
11568
  if (htmlPath) {
@@ -11507,10 +11874,10 @@ var createVoiceWorkflowContractHandler = (input) => {
11507
11874
  };
11508
11875
  };
11509
11876
  // src/turnLatency.ts
11510
- import { Elysia as Elysia20 } from "elysia";
11877
+ import { Elysia as Elysia21 } from "elysia";
11511
11878
  var DEFAULT_WARN_AFTER_MS = 1800;
11512
11879
  var DEFAULT_FAIL_AFTER_MS = 3200;
11513
- var escapeHtml21 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11880
+ var escapeHtml22 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11514
11881
  var firstNumber2 = (values) => values.filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
11515
11882
  var getString10 = (value) => typeof value === "string" && value.trim() ? value : undefined;
11516
11883
  var createTraceStageIndex = (events) => {
@@ -11624,11 +11991,11 @@ var summarizeVoiceTurnLatency = async (options) => {
11624
11991
  var formatMs2 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
11625
11992
  var renderVoiceTurnLatencyHTML = (report, options = {}) => {
11626
11993
  const title = options.title ?? "Voice Turn Latency";
11627
- const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml21(turn.status)}">
11628
- <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>
11629
- <dl>${turn.stages.map((stage) => `<div><dt>${escapeHtml21(stage.label)}</dt><dd>${escapeHtml21(formatMs2(stage.valueMs))}</dd></div>`).join("")}</dl>
11994
+ const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml22(turn.status)}">
11995
+ <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>
11996
+ <dl>${turn.stages.map((stage) => `<div><dt>${escapeHtml22(stage.label)}</dt><dd>${escapeHtml22(formatMs2(stage.valueMs))}</dd></div>`).join("")}</dl>
11630
11997
  </article>`).join("");
11631
- 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>`;
11998
+ 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>`;
11632
11999
  };
11633
12000
  var createVoiceTurnLatencyJSONHandler = (options) => async () => summarizeVoiceTurnLatency(options);
11634
12001
  var createVoiceTurnLatencyHTMLHandler = (options) => async () => {
@@ -11645,7 +12012,7 @@ var createVoiceTurnLatencyHTMLHandler = (options) => async () => {
11645
12012
  var createVoiceTurnLatencyRoutes = (options) => {
11646
12013
  const path = options.path ?? "/api/turn-latency";
11647
12014
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
11648
- const routes = new Elysia20({
12015
+ const routes = new Elysia21({
11649
12016
  name: options.name ?? "absolutejs-voice-turn-latency"
11650
12017
  }).get(path, createVoiceTurnLatencyJSONHandler(options));
11651
12018
  if (htmlPath) {
@@ -11654,8 +12021,8 @@ var createVoiceTurnLatencyRoutes = (options) => {
11654
12021
  return routes;
11655
12022
  };
11656
12023
  // src/liveLatency.ts
11657
- import { Elysia as Elysia21 } from "elysia";
11658
- var escapeHtml22 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
12024
+ import { Elysia as Elysia22 } from "elysia";
12025
+ var escapeHtml23 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11659
12026
  var percentile = (values, percentileValue) => {
11660
12027
  if (values.length === 0) {
11661
12028
  return;
@@ -11703,13 +12070,13 @@ var summarizeVoiceLiveLatency = async (options) => {
11703
12070
  var formatMs3 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
11704
12071
  var renderVoiceLiveLatencyHTML = (report, options = {}) => {
11705
12072
  const title = options.title ?? "Voice Live Latency";
11706
- 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("");
11707
- 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>`;
12073
+ 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("");
12074
+ 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>`;
11708
12075
  };
11709
12076
  var createVoiceLiveLatencyRoutes = (options) => {
11710
12077
  const path = options.path ?? "/api/live-latency";
11711
12078
  const htmlPath = options.htmlPath === undefined ? "/live-latency" : options.htmlPath;
11712
- const routes = new Elysia21({
12079
+ const routes = new Elysia22({
11713
12080
  name: options.name ?? "absolutejs-voice-live-latency"
11714
12081
  }).get(path, () => summarizeVoiceLiveLatency(options));
11715
12082
  if (htmlPath) {
@@ -11726,9 +12093,9 @@ var createVoiceLiveLatencyRoutes = (options) => {
11726
12093
  return routes;
11727
12094
  };
11728
12095
  // src/turnQuality.ts
11729
- import { Elysia as Elysia22 } from "elysia";
12096
+ import { Elysia as Elysia23 } from "elysia";
11730
12097
  var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
11731
- var escapeHtml23 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
12098
+ var escapeHtml24 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
11732
12099
  var getTurnLatencyMs = (turn) => {
11733
12100
  const firstTranscriptAt = turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
11734
12101
  if (firstTranscriptAt === undefined) {
@@ -11799,24 +12166,24 @@ var summarizeVoiceTurnQuality = async (options) => {
11799
12166
  };
11800
12167
  var renderVoiceTurnQualityHTML = (report, options = {}) => {
11801
12168
  const title = options.title ?? "Voice Turn Quality";
11802
- const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml23(turn.status)}">
12169
+ const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml24(turn.status)}">
11803
12170
  <div class="turn-header">
11804
12171
  <div>
11805
- <p class="eyebrow">${escapeHtml23(turn.sessionId)} \xB7 ${escapeHtml23(turn.turnId)}</p>
11806
- <h2>${escapeHtml23(turn.text || "Empty turn")}</h2>
12172
+ <p class="eyebrow">${escapeHtml24(turn.sessionId)} \xB7 ${escapeHtml24(turn.turnId)}</p>
12173
+ <h2>${escapeHtml24(turn.text || "Empty turn")}</h2>
11807
12174
  </div>
11808
- <strong>${escapeHtml23(turn.status)}</strong>
12175
+ <strong>${escapeHtml24(turn.status)}</strong>
11809
12176
  </div>
11810
12177
  <dl>
11811
- <div><dt>Source</dt><dd>${escapeHtml23(turn.source ?? "unknown")}</dd></div>
12178
+ <div><dt>Source</dt><dd>${escapeHtml24(turn.source ?? "unknown")}</dd></div>
11812
12179
  <div><dt>Confidence</dt><dd>${turn.averageConfidence === undefined ? "n/a" : `${Math.round(turn.averageConfidence * 100)}%`}</dd></div>
11813
- <div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml23(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
11814
- <div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${escapeHtml23(turn.correctionProvider)}` : ""}` : "none"}</dd></div>
12180
+ <div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml24(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
12181
+ <div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${escapeHtml24(turn.correctionProvider)}` : ""}` : "none"}</dd></div>
11815
12182
  <div><dt>Transcripts</dt><dd>${String(turn.selectedTranscriptCount)} selected \xB7 ${String(turn.finalTranscriptCount)} final \xB7 ${String(turn.partialTranscriptCount)} partial</dd></div>
11816
12183
  <div><dt>Cost</dt><dd>${turn.costUnits === undefined ? "n/a" : String(turn.costUnits)}</dd></div>
11817
12184
  </dl>
11818
12185
  </article>`).join("");
11819
- 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>`;
12186
+ 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>`;
11820
12187
  };
11821
12188
  var createVoiceTurnQualityJSONHandler = (options) => async () => summarizeVoiceTurnQuality(options);
11822
12189
  var createVoiceTurnQualityHTMLHandler = (options) => async () => {
@@ -11833,7 +12200,7 @@ var createVoiceTurnQualityHTMLHandler = (options) => async () => {
11833
12200
  var createVoiceTurnQualityRoutes = (options) => {
11834
12201
  const path = options.path ?? "/api/turn-quality";
11835
12202
  const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
11836
- const routes = new Elysia22({
12203
+ const routes = new Elysia23({
11837
12204
  name: options.name ?? "absolutejs-voice-turn-quality"
11838
12205
  }).get(path, createVoiceTurnQualityJSONHandler(options));
11839
12206
  if (htmlPath) {
@@ -11842,7 +12209,7 @@ var createVoiceTurnQualityRoutes = (options) => {
11842
12209
  return routes;
11843
12210
  };
11844
12211
  // src/telephonyOutcome.ts
11845
- import { Elysia as Elysia23 } from "elysia";
12212
+ import { Elysia as Elysia24 } from "elysia";
11846
12213
  var DEFAULT_COMPLETED_STATUSES = [
11847
12214
  "answered",
11848
12215
  "completed",
@@ -12489,7 +12856,7 @@ var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
12489
12856
  var createVoiceTelephonyWebhookRoutes = (options = {}) => {
12490
12857
  const path = options.path ?? "/api/voice/telephony/webhook";
12491
12858
  const handler = createVoiceTelephonyWebhookHandler(options);
12492
- return new Elysia23({
12859
+ return new Elysia24({
12493
12860
  name: options.name ?? "absolutejs-voice-telephony-webhooks"
12494
12861
  }).post(path, async ({ query, request }) => {
12495
12862
  try {
@@ -12510,15 +12877,15 @@ var createVoiceTelephonyWebhookRoutes = (options = {}) => {
12510
12877
  });
12511
12878
  };
12512
12879
  // src/phoneAgent.ts
12513
- import { Elysia as Elysia27 } from "elysia";
12880
+ import { Elysia as Elysia28 } from "elysia";
12514
12881
 
12515
12882
  // src/telephony/plivo.ts
12516
12883
  import { Buffer as Buffer4 } from "buffer";
12517
- import { Elysia as Elysia25 } from "elysia";
12884
+ import { Elysia as Elysia26 } from "elysia";
12518
12885
 
12519
12886
  // src/telephony/twilio.ts
12520
12887
  import { Buffer as Buffer3 } from "buffer";
12521
- import { Elysia as Elysia24 } from "elysia";
12888
+ import { Elysia as Elysia25 } from "elysia";
12522
12889
  var TWILIO_MULAW_SAMPLE_RATE = 8000;
12523
12890
  var VOICE_PCM_SAMPLE_RATE = 16000;
12524
12891
  var escapeXml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
@@ -12548,7 +12915,7 @@ var resolveTwilioStreamParameters = async (parameters, input) => {
12548
12915
  return parameters;
12549
12916
  };
12550
12917
  var joinUrlPath = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
12551
- var escapeHtml24 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
12918
+ var escapeHtml25 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
12552
12919
  var getWebhookVerificationUrl = (webhook, input) => {
12553
12920
  if (!webhook?.verificationUrl) {
12554
12921
  return;
@@ -12591,23 +12958,23 @@ var buildTwilioVoiceSetupStatus = async (options, input) => {
12591
12958
  };
12592
12959
  var renderTwilioVoiceSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
12593
12960
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio setup</p>
12594
- <h1>${escapeHtml24(title)}</h1>
12961
+ <h1>${escapeHtml25(title)}</h1>
12595
12962
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
12596
12963
  <section>
12597
12964
  <h2>URLs</h2>
12598
12965
  <ul>
12599
- <li><strong>TwiML:</strong> <code>${escapeHtml24(status.urls.twiml)}</code></li>
12600
- <li><strong>Media stream:</strong> <code>${escapeHtml24(status.urls.stream)}</code></li>
12601
- <li><strong>Status webhook:</strong> <code>${escapeHtml24(status.urls.webhook)}</code></li>
12966
+ <li><strong>TwiML:</strong> <code>${escapeHtml25(status.urls.twiml)}</code></li>
12967
+ <li><strong>Media stream:</strong> <code>${escapeHtml25(status.urls.stream)}</code></li>
12968
+ <li><strong>Status webhook:</strong> <code>${escapeHtml25(status.urls.webhook)}</code></li>
12602
12969
  </ul>
12603
12970
  </section>
12604
12971
  <section>
12605
12972
  <h2>Signing</h2>
12606
12973
  <p>Mode: <code>${status.signing.mode}</code></p>
12607
- ${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml24(status.signing.verificationUrl)}</code></p>` : ""}
12974
+ ${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml25(status.signing.verificationUrl)}</code></p>` : ""}
12608
12975
  </section>
12609
- ${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml24(name)}</code></li>`).join("")}</ul></section>` : ""}
12610
- ${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml24(warning)}</li>`).join("")}</ul></section>` : ""}
12976
+ ${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml25(name)}</code></li>`).join("")}</ul></section>` : ""}
12977
+ ${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml25(warning)}</li>`).join("")}</ul></section>` : ""}
12611
12978
  </main>`;
12612
12979
  var extractTwilioStreamUrl = (twiml) => twiml.match(/<Stream\b[^>]*\surl="([^"]+)"/i)?.[1]?.replaceAll("&amp;", "&");
12613
12980
  var createSmokeCheck = (name, status, message, details) => ({
@@ -12618,20 +12985,20 @@ var createSmokeCheck = (name, status, message, details) => ({
12618
12985
  });
12619
12986
  var renderTwilioVoiceSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
12620
12987
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio smoke test</p>
12621
- <h1>${escapeHtml24(title)}</h1>
12988
+ <h1>${escapeHtml25(title)}</h1>
12622
12989
  <p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
12623
12990
  <section>
12624
12991
  <h2>Checks</h2>
12625
12992
  <ul>
12626
- ${report.checks.map((check) => `<li><strong>${escapeHtml24(check.name)}</strong>: ${escapeHtml24(check.status)}${check.message ? ` - ${escapeHtml24(check.message)}` : ""}</li>`).join("")}
12993
+ ${report.checks.map((check) => `<li><strong>${escapeHtml25(check.name)}</strong>: ${escapeHtml25(check.status)}${check.message ? ` - ${escapeHtml25(check.message)}` : ""}</li>`).join("")}
12627
12994
  </ul>
12628
12995
  </section>
12629
12996
  <section>
12630
12997
  <h2>Observed URLs</h2>
12631
12998
  <ul>
12632
- <li><strong>TwiML:</strong> <code>${escapeHtml24(report.setup.urls.twiml)}</code></li>
12633
- <li><strong>Stream:</strong> <code>${escapeHtml24(report.twiml?.streamUrl ?? report.setup.urls.stream)}</code></li>
12634
- <li><strong>Webhook:</strong> <code>${escapeHtml24(report.setup.urls.webhook)}</code></li>
12999
+ <li><strong>TwiML:</strong> <code>${escapeHtml25(report.setup.urls.twiml)}</code></li>
13000
+ <li><strong>Stream:</strong> <code>${escapeHtml25(report.twiml?.streamUrl ?? report.setup.urls.stream)}</code></li>
13001
+ <li><strong>Webhook:</strong> <code>${escapeHtml25(report.setup.urls.webhook)}</code></li>
12635
13002
  </ul>
12636
13003
  </section>
12637
13004
  </main>`;
@@ -13091,7 +13458,7 @@ var createTwilioVoiceRoutes = (options) => {
13091
13458
  const smokePath = options.smoke?.path === false ? false : options.smoke?.path ?? "/api/voice/twilio/smoke";
13092
13459
  const bridges = new WeakMap;
13093
13460
  const webhookPolicy = options.webhook?.policy ?? options.outcomePolicy ?? createVoiceTelephonyOutcomePolicy();
13094
- const app = new Elysia24({
13461
+ const app = new Elysia25({
13095
13462
  name: options.name ?? "absolutejs-voice-twilio"
13096
13463
  }).get(twimlPath, async ({ query, request }) => {
13097
13464
  const streamUrl = await resolveTwilioStreamUrl(options, {
@@ -13228,7 +13595,7 @@ var createTwilioVoiceRoutes = (options) => {
13228
13595
 
13229
13596
  // src/telephony/plivo.ts
13230
13597
  var escapeXml3 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
13231
- var escapeHtml25 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
13598
+ var escapeHtml26 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
13232
13599
  var joinUrlPath2 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
13233
13600
  var resolveRequestOrigin2 = (request) => {
13234
13601
  const url = new URL(request.url);
@@ -13479,21 +13846,21 @@ var buildPlivoVoiceSetupStatus = async (options, input) => {
13479
13846
  };
13480
13847
  var renderPlivoSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
13481
13848
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo setup</p>
13482
- <h1>${escapeHtml25(title)}</h1>
13849
+ <h1>${escapeHtml26(title)}</h1>
13483
13850
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
13484
13851
  <ul>
13485
- <li><strong>Answer XML:</strong> <code>${escapeHtml25(status.urls.answer)}</code></li>
13486
- <li><strong>Audio stream:</strong> <code>${escapeHtml25(status.urls.stream)}</code></li>
13487
- <li><strong>Status webhook:</strong> <code>${escapeHtml25(status.urls.webhook)}</code></li>
13852
+ <li><strong>Answer XML:</strong> <code>${escapeHtml26(status.urls.answer)}</code></li>
13853
+ <li><strong>Audio stream:</strong> <code>${escapeHtml26(status.urls.stream)}</code></li>
13854
+ <li><strong>Status webhook:</strong> <code>${escapeHtml26(status.urls.webhook)}</code></li>
13488
13855
  </ul>
13489
- ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml25(name)}</code></li>`).join("")}</ul>` : ""}
13490
- ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml25(warning)}</li>`).join("")}</ul>` : ""}
13856
+ ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml26(name)}</code></li>`).join("")}</ul>` : ""}
13857
+ ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml26(warning)}</li>`).join("")}</ul>` : ""}
13491
13858
  </main>`;
13492
13859
  var renderPlivoSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
13493
13860
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo smoke test</p>
13494
- <h1>${escapeHtml25(title)}</h1>
13861
+ <h1>${escapeHtml26(title)}</h1>
13495
13862
  <p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
13496
- <ul>${report.checks.map((check) => `<li><strong>${escapeHtml25(check.name)}</strong>: ${escapeHtml25(check.status)}${check.message ? ` - ${escapeHtml25(check.message)}` : ""}</li>`).join("")}</ul>
13863
+ <ul>${report.checks.map((check) => `<li><strong>${escapeHtml26(check.name)}</strong>: ${escapeHtml26(check.status)}${check.message ? ` - ${escapeHtml26(check.message)}` : ""}</li>`).join("")}</ul>
13497
13864
  </main>`;
13498
13865
  var runPlivoSmokeTest = async (input) => {
13499
13866
  const setup = await buildPlivoVoiceSetupStatus(input.options, input);
@@ -13588,7 +13955,7 @@ var createPlivoVoiceRoutes = (options = {}) => {
13588
13955
  request: input.request
13589
13956
  }) : verificationUrl ?? input.request.url
13590
13957
  }) : undefined);
13591
- const app = new Elysia25({
13958
+ const app = new Elysia26({
13592
13959
  name: options.name ?? "absolutejs-voice-plivo"
13593
13960
  }).get(answerPath, async ({ query, request }) => {
13594
13961
  const streamUrl = await resolvePlivoStreamUrl(options, {
@@ -13699,9 +14066,9 @@ var createPlivoVoiceRoutes = (options = {}) => {
13699
14066
 
13700
14067
  // src/telephony/telnyx.ts
13701
14068
  import { Buffer as Buffer5 } from "buffer";
13702
- import { Elysia as Elysia26 } from "elysia";
14069
+ import { Elysia as Elysia27 } from "elysia";
13703
14070
  var escapeXml4 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
13704
- var escapeHtml26 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
14071
+ var escapeHtml27 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
13705
14072
  var joinUrlPath3 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
13706
14073
  var resolveRequestOrigin3 = (request) => {
13707
14074
  const url = new URL(request.url);
@@ -13902,21 +14269,21 @@ var buildTelnyxVoiceSetupStatus = async (options, input) => {
13902
14269
  };
13903
14270
  var renderTelnyxSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
13904
14271
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx setup</p>
13905
- <h1>${escapeHtml26(title)}</h1>
14272
+ <h1>${escapeHtml27(title)}</h1>
13906
14273
  <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
13907
14274
  <ul>
13908
- <li><strong>TeXML:</strong> <code>${escapeHtml26(status.urls.texml)}</code></li>
13909
- <li><strong>Media stream:</strong> <code>${escapeHtml26(status.urls.stream)}</code></li>
13910
- <li><strong>Status webhook:</strong> <code>${escapeHtml26(status.urls.webhook)}</code></li>
14275
+ <li><strong>TeXML:</strong> <code>${escapeHtml27(status.urls.texml)}</code></li>
14276
+ <li><strong>Media stream:</strong> <code>${escapeHtml27(status.urls.stream)}</code></li>
14277
+ <li><strong>Status webhook:</strong> <code>${escapeHtml27(status.urls.webhook)}</code></li>
13911
14278
  </ul>
13912
- ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml26(name)}</code></li>`).join("")}</ul>` : ""}
13913
- ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml26(warning)}</li>`).join("")}</ul>` : ""}
14279
+ ${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml27(name)}</code></li>`).join("")}</ul>` : ""}
14280
+ ${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml27(warning)}</li>`).join("")}</ul>` : ""}
13914
14281
  </main>`;
13915
14282
  var renderTelnyxSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
13916
14283
  <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx smoke test</p>
13917
- <h1>${escapeHtml26(title)}</h1>
14284
+ <h1>${escapeHtml27(title)}</h1>
13918
14285
  <p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
13919
- <ul>${report.checks.map((check) => `<li><strong>${escapeHtml26(check.name)}</strong>: ${escapeHtml26(check.status)}${check.message ? ` - ${escapeHtml26(check.message)}` : ""}</li>`).join("")}</ul>
14286
+ <ul>${report.checks.map((check) => `<li><strong>${escapeHtml27(check.name)}</strong>: ${escapeHtml27(check.status)}${check.message ? ` - ${escapeHtml27(check.message)}` : ""}</li>`).join("")}</ul>
13920
14287
  </main>`;
13921
14288
  var runTelnyxSmokeTest = async (input) => {
13922
14289
  const setup = await buildTelnyxVoiceSetupStatus(input.options, input);
@@ -14010,7 +14377,7 @@ var createTelnyxVoiceRoutes = (options = {}) => {
14010
14377
  publicKey: options.webhook?.publicKey,
14011
14378
  toleranceSeconds: options.webhook?.toleranceSeconds
14012
14379
  }) : undefined);
14013
- const app = new Elysia26({
14380
+ const app = new Elysia27({
14014
14381
  name: options.name ?? "absolutejs-voice-telnyx"
14015
14382
  }).get(texmlPath, async ({ query, request }) => {
14016
14383
  const streamUrl = await resolveTelnyxStreamUrl(options, {
@@ -14180,7 +14547,7 @@ var createVoicePhoneAgent = (options) => {
14180
14547
  setupPath: resolveSetupPath(carrier),
14181
14548
  smokePath: resolveSmokePath(carrier)
14182
14549
  }));
14183
- const app = new Elysia27({
14550
+ const app = new Elysia28({
14184
14551
  name: options.name ?? "absolutejs-voice-phone-agent"
14185
14552
  });
14186
14553
  for (const carrier of options.carriers) {
@@ -16461,7 +16828,7 @@ var createVoiceMemoryStore = () => {
16461
16828
  return { get, getOrCreate, list, remove, set };
16462
16829
  };
16463
16830
  // src/opsWebhook.ts
16464
- import { Elysia as Elysia28 } from "elysia";
16831
+ import { Elysia as Elysia29 } from "elysia";
16465
16832
  var toHex5 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
16466
16833
  var signVoiceOpsWebhookBody = async (input) => {
16467
16834
  const encoder = new TextEncoder;
@@ -16591,7 +16958,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
16591
16958
  };
16592
16959
  var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
16593
16960
  const path = options.path ?? "/api/voice-ops/webhook";
16594
- return new Elysia28().post(path, async ({ body, request, set }) => {
16961
+ return new Elysia29().post(path, async ({ body, request, set }) => {
16595
16962
  const bodyText = typeof body === "string" ? body : JSON.stringify(body);
16596
16963
  if (options.signingSecret) {
16597
16964
  const verification = await verifyVoiceOpsWebhookSignature({
@@ -18400,6 +18767,7 @@ export {
18400
18767
  summarizeVoiceIntegrationEvents,
18401
18768
  summarizeVoiceHandoffHealth,
18402
18769
  summarizeVoiceHandoffDeliveries,
18770
+ summarizeVoiceCampaigns,
18403
18771
  summarizeVoiceBargeIn,
18404
18772
  summarizeVoiceAssistantRuns,
18405
18773
  summarizeVoiceAssistantHealth,
@@ -18455,6 +18823,7 @@ export {
18455
18823
  renderVoiceHandoffHealthHTML,
18456
18824
  renderVoiceEvalHTML,
18457
18825
  renderVoiceEvalBaselineHTML,
18826
+ renderVoiceCampaignsHTML,
18458
18827
  renderVoiceCallReviewMarkdown,
18459
18828
  renderVoiceCallReviewHTML,
18460
18829
  renderVoiceBargeInHTML,
@@ -18590,6 +18959,7 @@ export {
18590
18959
  createVoiceMemoryTraceEventStore,
18591
18960
  createVoiceMemoryStore,
18592
18961
  createVoiceMemoryHandoffDeliveryStore,
18962
+ createVoiceMemoryCampaignStore,
18593
18963
  createVoiceMemoryAssistantMemoryStore,
18594
18964
  createVoiceLiveLatencyRoutes,
18595
18965
  createVoiceLinearIssueUpdateSink,
@@ -18625,6 +18995,8 @@ export {
18625
18995
  createVoiceExperiment,
18626
18996
  createVoiceEvalRoutes,
18627
18997
  createVoiceDiagnosticsRoutes,
18998
+ createVoiceCampaignRoutes,
18999
+ createVoiceCampaign,
18628
19000
  createVoiceCallReviewRecorder,
18629
19001
  createVoiceCallReviewFromSession,
18630
19002
  createVoiceCallReviewFromLiveTelephonyReport,