@absolutejs/voice 0.0.22-beta.205 → 0.0.22-beta.207
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -1
- package/dist/index.js +45 -7
- package/dist/phoneAgent.d.ts +14 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -851,7 +851,30 @@ The wrapper mounts selected carrier routes plus two proof surfaces:
|
|
|
851
851
|
- `/api/voice/phone/smoke-contract?sessionId=...`: trace-backed production smoke contract.
|
|
852
852
|
- `/voice/phone/smoke-contract?sessionId=...`: HTML production smoke contract.
|
|
853
853
|
|
|
854
|
-
The setup
|
|
854
|
+
The setup JSON includes `setupInstructions`, so your own admin UI can render copy-ready carrier fields without scraping HTML:
|
|
855
|
+
|
|
856
|
+
```ts
|
|
857
|
+
const setup = await fetch('/api/voice/phone/setup').then((response) =>
|
|
858
|
+
response.json()
|
|
859
|
+
);
|
|
860
|
+
|
|
861
|
+
for (const carrier of setup.setupInstructions) {
|
|
862
|
+
console.log(carrier.carrierName, carrier.status);
|
|
863
|
+
console.log(carrier.steps.join('\n'));
|
|
864
|
+
}
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
Each instruction includes:
|
|
868
|
+
|
|
869
|
+
- `answerLabel`: `TwiML URL`, `TeXML URL`, or `Answer URL`.
|
|
870
|
+
- `answerUrl`: the URL to paste into the carrier's inbound voice/answer field.
|
|
871
|
+
- `webhookUrl`: the status callback or webhook URL.
|
|
872
|
+
- `streamUrl`: the `wss://` media stream URL the carrier must reach.
|
|
873
|
+
- `setupPath` and `smokePath`: package-mounted proof pages for that carrier.
|
|
874
|
+
- `steps`: ordered copy/paste guidance for the carrier dashboard.
|
|
875
|
+
- `issues`: contract errors or warnings from the carrier matrix.
|
|
876
|
+
|
|
877
|
+
The setup page renders the same instructions and tells you exactly what to copy into the carrier dashboard:
|
|
855
878
|
|
|
856
879
|
- Twilio: set the phone number voice webhook/TwiML URL to the reported TwiML URL, set the status callback to the reported webhook URL, and allow the reported `wss://` media stream.
|
|
857
880
|
- Telnyx: set the connection TeXML URL to the reported TeXML URL, set the status webhook to the reported webhook URL, and allow the reported `wss://` media stream.
|
|
@@ -1721,6 +1744,91 @@ Mounted routes:
|
|
|
1721
1744
|
|
|
1722
1745
|
`createVoiceZeroRetentionPolicy(...)` intentionally defaults to `dryRun: true`; callers must explicitly apply the generated policy after reviewing the deletion proof. This gives compliance-sensitive deployments a concrete zero-retention recipe without making accidental deletion easy.
|
|
1723
1746
|
|
|
1747
|
+
### Compliance Recipes
|
|
1748
|
+
|
|
1749
|
+
These are recipes, not compliance certifications. AbsoluteJS Voice gives you the self-hosted controls and proof surfaces; your legal/security team still owns the actual HIPAA, SOC 2, GDPR, or customer-contract process.
|
|
1750
|
+
|
|
1751
|
+
Zero-retention sensitive call:
|
|
1752
|
+
|
|
1753
|
+
```ts
|
|
1754
|
+
const policy = createVoiceZeroRetentionPolicy({
|
|
1755
|
+
...runtimeStorage,
|
|
1756
|
+
audit: runtimeStorage.audit,
|
|
1757
|
+
auditDeliveries: runtimeStorage.auditDeliveries,
|
|
1758
|
+
traceDeliveries: runtimeStorage.traceDeliveries
|
|
1759
|
+
});
|
|
1760
|
+
|
|
1761
|
+
const dryRun = await buildVoiceDataRetentionPlan(policy);
|
|
1762
|
+
if (dryRun.deletedCount > 0) {
|
|
1763
|
+
await applyVoiceDataRetentionPolicy({
|
|
1764
|
+
...policy,
|
|
1765
|
+
dryRun: false
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
```
|
|
1769
|
+
|
|
1770
|
+
This removes sessions, traces, reviews, tasks, integration events, campaigns, incident bundles, and delivery queues that match the policy selectors. The generated policy starts as a dry run so a zero-retention mode cannot accidentally wipe data without explicit application.
|
|
1771
|
+
|
|
1772
|
+
Redacted support export:
|
|
1773
|
+
|
|
1774
|
+
```ts
|
|
1775
|
+
const auditExport = await exportVoiceAuditTrail({
|
|
1776
|
+
redact: voiceComplianceRedactionDefaults,
|
|
1777
|
+
store: runtimeStorage.audit
|
|
1778
|
+
});
|
|
1779
|
+
const auditMarkdown = renderVoiceAuditMarkdown(auditExport.events);
|
|
1780
|
+
|
|
1781
|
+
const traceMarkdown = renderVoiceTraceMarkdown(events, {
|
|
1782
|
+
redact: voiceComplianceRedactionDefaults
|
|
1783
|
+
});
|
|
1784
|
+
```
|
|
1785
|
+
|
|
1786
|
+
Use this for support tickets, customer escalations, incident reviews, or vendor handoffs where transcripts, tool payloads, provider metadata, or audit events may contain personal data.
|
|
1787
|
+
|
|
1788
|
+
Customer-owned storage:
|
|
1789
|
+
|
|
1790
|
+
```ts
|
|
1791
|
+
const runtimeStorage = createVoicePostgresRuntimeStorage({
|
|
1792
|
+
connectionString: process.env.DATABASE_URL!,
|
|
1793
|
+
schemaName: 'voice_ops',
|
|
1794
|
+
tablePrefix: 'support'
|
|
1795
|
+
});
|
|
1796
|
+
|
|
1797
|
+
app.use(
|
|
1798
|
+
createVoiceDataControlRoutes({
|
|
1799
|
+
...runtimeStorage,
|
|
1800
|
+
audit: runtimeStorage.audit,
|
|
1801
|
+
auditDeliveries: runtimeStorage.auditDeliveries,
|
|
1802
|
+
redact: voiceComplianceRedactionDefaults,
|
|
1803
|
+
traceDeliveries: runtimeStorage.traceDeliveries
|
|
1804
|
+
})
|
|
1805
|
+
);
|
|
1806
|
+
```
|
|
1807
|
+
|
|
1808
|
+
Use file storage for local demos, SQLite for small self-hosted installs, Postgres for production app-owned records, and S3 delivery for exported audit/trace evidence. The important point is that sessions, traces, reviews, tasks, campaigns, audit, and delivery queues remain in infrastructure the app owner controls.
|
|
1809
|
+
|
|
1810
|
+
Deploy gate for compliance evidence:
|
|
1811
|
+
|
|
1812
|
+
```ts
|
|
1813
|
+
app.use(
|
|
1814
|
+
createVoiceProductionReadinessRoutes({
|
|
1815
|
+
audit: {
|
|
1816
|
+
require: [
|
|
1817
|
+
{ type: 'provider.call' },
|
|
1818
|
+
{ type: 'operator.action' },
|
|
1819
|
+
{ type: 'retention.policy', maxAgeMs: 7 * 24 * 60 * 60 * 1000 }
|
|
1820
|
+
],
|
|
1821
|
+
store: runtimeStorage.audit
|
|
1822
|
+
},
|
|
1823
|
+
auditDeliveries: runtimeStorage.auditDeliveries,
|
|
1824
|
+
traceDeliveries: runtimeStorage.traceDeliveries,
|
|
1825
|
+
store: runtimeStorage.traces
|
|
1826
|
+
})
|
|
1827
|
+
);
|
|
1828
|
+
```
|
|
1829
|
+
|
|
1830
|
+
This makes provider-call audit evidence, operator interventions, recent retention-policy proof, and export-queue health part of release readiness instead of a manual dashboard check.
|
|
1831
|
+
|
|
1724
1832
|
Use `createVoiceAuditLogger(...)` when you need append-only compliance evidence outside call traces. The logger records provider calls, tool calls, handoffs, retention runs, and operator actions into `runtimeStorage.audit`, so self-hosted teams can prove who changed what, which provider ran, which tool fired, and what data-control policy deleted.
|
|
1725
1833
|
|
|
1726
1834
|
Pass `audit` directly to `createVoiceAgent(...)` to record model calls as provider-call audit events and tool executions as tool-call audit events. Pass it to `createVoiceAgentSquad(...)` to record squad handoffs automatically. Use `auditProvider` and `auditModel` on agents when you want readiness and compliance reports to show the actual model provider instead of the agent id.
|
package/dist/index.js
CHANGED
|
@@ -19096,6 +19096,41 @@ var loadCarrierMatrixInputs = async (input) => {
|
|
|
19096
19096
|
}
|
|
19097
19097
|
return entries;
|
|
19098
19098
|
};
|
|
19099
|
+
var carrierAnswerLabel = (provider) => provider === "telnyx" ? "TeXML URL" : provider === "plivo" ? "Answer URL" : "TwiML URL";
|
|
19100
|
+
var findCarrierMatrixEntry = (matrix, carrier) => matrix?.entries.find((candidate) => candidate.provider === carrier.provider && (candidate.name === carrier.name || candidate.name === (carrier.name ?? carrier.provider)));
|
|
19101
|
+
var buildVoicePhoneAgentSetupInstructions = (input) => input.carriers.map((carrier) => {
|
|
19102
|
+
const entry = findCarrierMatrixEntry(input.matrix, carrier);
|
|
19103
|
+
const urls = entry?.setup.urls;
|
|
19104
|
+
const answerLabel = carrierAnswerLabel(carrier.provider);
|
|
19105
|
+
const urlRecord = urls ?? {};
|
|
19106
|
+
const answerUrl = urlRecord.twiml ?? urlRecord.texml ?? urlRecord.answer;
|
|
19107
|
+
const webhookUrl = urls?.webhook;
|
|
19108
|
+
const streamUrl = urls?.stream;
|
|
19109
|
+
const setupPath = typeof carrier.setupPath === "string" ? `${carrier.setupPath}?format=html` : undefined;
|
|
19110
|
+
const smokePath = typeof carrier.smokePath === "string" ? `${carrier.smokePath}?format=html` : undefined;
|
|
19111
|
+
const productionSmokePath = input.productionSmokePath ? `${input.productionSmokePath.replace("/api/", "/")}?sessionId=` : undefined;
|
|
19112
|
+
const steps = [
|
|
19113
|
+
`Set ${answerLabel} to ${answerUrl ?? "missing"}.`,
|
|
19114
|
+
`Set status webhook to ${webhookUrl ?? "missing"}.`,
|
|
19115
|
+
`Allow media stream URL ${streamUrl ?? "missing"}.`,
|
|
19116
|
+
...setupPath ? [`Open setup report ${setupPath}.`] : [],
|
|
19117
|
+
...smokePath ? [`Run carrier smoke ${smokePath}.`] : [],
|
|
19118
|
+
...productionSmokePath ? [`Certify production smoke traces at ${productionSmokePath}.`] : []
|
|
19119
|
+
];
|
|
19120
|
+
return {
|
|
19121
|
+
answerLabel,
|
|
19122
|
+
answerUrl,
|
|
19123
|
+
carrierName: carrier.name ?? carrier.provider,
|
|
19124
|
+
issues: entry?.issues.map((issue) => `${issue.severity}: ${issue.message}`) ?? [],
|
|
19125
|
+
provider: carrier.provider,
|
|
19126
|
+
setupPath: carrier.setupPath,
|
|
19127
|
+
smokePath: carrier.smokePath,
|
|
19128
|
+
status: entry?.status ?? "unknown",
|
|
19129
|
+
steps,
|
|
19130
|
+
streamUrl,
|
|
19131
|
+
webhookUrl
|
|
19132
|
+
};
|
|
19133
|
+
});
|
|
19099
19134
|
var resolveCarrierContract = async (input) => {
|
|
19100
19135
|
const matrixInputs = await loadCarrierMatrixInputs({
|
|
19101
19136
|
app: input.app,
|
|
@@ -19111,7 +19146,7 @@ var resolveCarrierContract = async (input) => {
|
|
|
19111
19146
|
};
|
|
19112
19147
|
var renderVoicePhoneAgentSetupHTML = (report) => {
|
|
19113
19148
|
const carrierRows = report.carriers.map((carrier) => {
|
|
19114
|
-
const entry = report.matrix
|
|
19149
|
+
const entry = findCarrierMatrixEntry(report.matrix, carrier);
|
|
19115
19150
|
const urls = entry?.setup.urls;
|
|
19116
19151
|
const primaryUrl = carrier.provider === "plivo" ? urls?.twiml : urls?.twiml;
|
|
19117
19152
|
return `<tr><td>${escapeHtml33(carrier.name ?? carrier.provider)}</td><td>${escapeHtml33(carrier.provider)}</td><td><code>${escapeHtml33(carrier.setupPath || "disabled")}</code></td><td><code>${escapeHtml33(carrier.smokePath || "disabled")}</code></td><td>${entry ? `<span class="${escapeHtml33(entry.status)}">${escapeHtml33(entry.status.toUpperCase())}</span>` : "unknown"}</td><td>${primaryUrl ? `<code>${escapeHtml33(primaryUrl)}</code>` : '<span class="muted">missing</span>'}</td><td>${urls?.webhook ? `<code>${escapeHtml33(urls.webhook)}</code>` : '<span class="muted">missing</span>'}</td><td>${urls?.stream ? `<code>${escapeHtml33(urls.stream)}</code>` : '<span class="muted">missing</span>'}</td></tr>`;
|
|
@@ -19150,12 +19185,10 @@ app.use(
|
|
|
19150
19185
|
})
|
|
19151
19186
|
);`);
|
|
19152
19187
|
const checklist = report.carriers.map((carrier) => {
|
|
19153
|
-
const
|
|
19154
|
-
const
|
|
19155
|
-
const
|
|
19156
|
-
|
|
19157
|
-
const issueList = entry?.issues.map((issue) => `<li>${escapeHtml33(issue.severity)}: ${escapeHtml33(issue.message)}</li>`).join("") ?? "";
|
|
19158
|
-
return `<article><h3>${escapeHtml33(carrier.name ?? carrier.provider)}</h3><ol><li>Set ${escapeHtml33(answerLabel)} to <code>${escapeHtml33(answerUrl ?? "missing")}</code>.</li><li>Set status webhook to <code>${escapeHtml33(urls?.webhook ?? "missing")}</code>.</li><li>Allow media stream URL <code>${escapeHtml33(urls?.stream ?? "missing")}</code>.</li><li>Open setup: ${carrier.setupPath ? `<a href="${escapeHtml33(carrier.setupPath)}?format=html">${escapeHtml33(carrier.setupPath)}</a>` : '<span class="muted">disabled</span>'}.</li><li>Run smoke: ${carrier.smokePath ? `<a href="${escapeHtml33(carrier.smokePath)}?format=html">${escapeHtml33(carrier.smokePath)}</a>` : '<span class="muted">disabled</span>'}.</li>${report.productionSmokePath ? `<li>Certify production smoke traces: <a href="${escapeHtml33(report.productionSmokePath.replace("/api/", "/"))}?sessionId=">${escapeHtml33(report.productionSmokePath)}</a>.</li>` : ""}</ol>${issueList ? `<ul class="issues">${issueList}</ul>` : '<p class="pass">No carrier contract issues.</p>'}</article>`;
|
|
19188
|
+
const instruction = report.setupInstructions.find((candidate) => candidate.provider === carrier.provider && candidate.carrierName === (carrier.name ?? carrier.provider));
|
|
19189
|
+
const issueList = instruction?.issues.map((issue) => `<li>${escapeHtml33(issue)}</li>`).join("") ?? "";
|
|
19190
|
+
const steps = instruction?.steps.map((step) => `<li>${escapeHtml33(step)}</li>`).join("") ?? "";
|
|
19191
|
+
return `<article><h3>${escapeHtml33(carrier.name ?? carrier.provider)}</h3><ol>${steps}</ol>${issueList ? `<ul class="issues">${issueList}</ul>` : '<p class="pass">No carrier contract issues.</p>'}</article>`;
|
|
19159
19192
|
}).join("");
|
|
19160
19193
|
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml33(report.title)}</title><style>body{background:#10151c;color:#f8f3e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.primitive{background:linear-gradient(135deg,rgba(20,184,166,.2),rgba(245,158,11,.12));border:1px solid #283544;border-radius:28px;margin-bottom:18px;padding:28px}.primitive{background:#151d27;border-color:#365a60}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,4.8rem);line-height:.92;margin:.2rem 0 1rem}.badge{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;padding:8px 12px}.pass{color:#86efac}.fail{color:#fca5a5}.warn{color:#fde68a}.muted{color:#aab5c0}table{background:#151d27;border:1px solid #283544;border-collapse:collapse;border-radius:18px;display:block;overflow:auto;width:100%}td,th{border-bottom:1px solid #283544;padding:12px;text-align:left;vertical-align:top}code{color:#fde68a;overflow-wrap:anywhere}.primitive p{color:#cbd5de;line-height:1.55}.primitive pre{background:#0b1118;border:1px solid #283544;border-radius:18px;color:#fef3c7;overflow:auto;padding:16px}.checklist{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));margin:18px 0}.checklist article{background:#151d27;border:1px solid #283544;border-radius:18px;padding:18px}.checklist ol{padding-left:20px}.issues{color:#fca5a5}.stages{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));padding-left:18px}a{color:#5eead4}</style></head><body><main><section class="hero"><p class="eyebrow">Phone agent setup</p><h1>${escapeHtml33(report.title)}</h1><p>One self-hosted entrypoint for carrier routes, setup reports, smoke checks, and normalized call lifecycle stages.</p><p class="badge ${report.ready ? "pass" : "fail"}">Ready: ${String(report.ready)}</p>${report.matrixPath ? `<p><a href="${escapeHtml33(report.matrixPath)}?format=html">Open carrier matrix</a></p>` : ""}</section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoicePhoneAgent(...)</code> builds this carrier control plane</h2><p>Mount carrier routes once, expose setup and smoke proof, then feed the same carrier matrix and phone-agent smoke reports into production readiness so carrier regressions block deploys.</p><pre><code>${snippet}</code></pre></section><h2>Carrier Setup Checklist</h2><section class="checklist">${checklist}</section><h2>Carrier URLs</h2><table><thead><tr><th>Name</th><th>Provider</th><th>Setup</th><th>Smoke</th><th>Status</th><th>Answer/TwiML/TeXML</th><th>Webhook</th><th>Stream</th></tr></thead><tbody>${carrierRows}</tbody></table><h2>Lifecycle Schema</h2><ul class="stages">${stageList}</ul></main></body></html>`;
|
|
19161
19194
|
};
|
|
@@ -19230,6 +19263,11 @@ var createVoicePhoneAgent = (options) => {
|
|
|
19230
19263
|
matrixPath,
|
|
19231
19264
|
productionSmokePath,
|
|
19232
19265
|
ready: matrix ? matrix.pass : carrierSummaries.length > 0,
|
|
19266
|
+
setupInstructions: buildVoicePhoneAgentSetupInstructions({
|
|
19267
|
+
carriers: carrierSummaries,
|
|
19268
|
+
matrix,
|
|
19269
|
+
productionSmokePath
|
|
19270
|
+
}),
|
|
19233
19271
|
setupPath,
|
|
19234
19272
|
title: setupTitle
|
|
19235
19273
|
};
|
package/dist/phoneAgent.d.ts
CHANGED
|
@@ -40,6 +40,19 @@ export type VoicePhoneAgentCarrierSummary = {
|
|
|
40
40
|
setupPath?: false | string;
|
|
41
41
|
smokePath?: false | string;
|
|
42
42
|
};
|
|
43
|
+
export type VoicePhoneAgentSetupInstruction = {
|
|
44
|
+
answerLabel: string;
|
|
45
|
+
answerUrl?: string;
|
|
46
|
+
carrierName: string;
|
|
47
|
+
issues: string[];
|
|
48
|
+
provider: 'plivo' | 'telnyx' | 'twilio';
|
|
49
|
+
setupPath?: false | string;
|
|
50
|
+
smokePath?: false | string;
|
|
51
|
+
status: 'fail' | 'pass' | 'unknown' | 'warn';
|
|
52
|
+
steps: string[];
|
|
53
|
+
streamUrl?: string;
|
|
54
|
+
webhookUrl?: string;
|
|
55
|
+
};
|
|
43
56
|
export type VoicePhoneAgentRoutes = {
|
|
44
57
|
carriers: VoicePhoneAgentCarrierSummary[];
|
|
45
58
|
matrixPath?: string;
|
|
@@ -55,6 +68,7 @@ export type VoicePhoneAgentSetupReport = {
|
|
|
55
68
|
matrixPath?: string;
|
|
56
69
|
productionSmokePath?: string;
|
|
57
70
|
ready: boolean;
|
|
71
|
+
setupInstructions: VoicePhoneAgentSetupInstruction[];
|
|
58
72
|
setupPath?: string;
|
|
59
73
|
title: string;
|
|
60
74
|
};
|