@absolutejs/voice 0.0.22-beta.112 → 0.0.22-beta.113
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/campaign.d.ts +77 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +123 -1
- package/package.json +1 -1
package/dist/campaign.d.ts
CHANGED
|
@@ -154,6 +154,7 @@ export type VoiceCampaignRuntime = {
|
|
|
154
154
|
export type VoiceCampaignRoutesOptions = VoiceCampaignRuntimeOptions & {
|
|
155
155
|
headers?: HeadersInit;
|
|
156
156
|
htmlPath?: false | string;
|
|
157
|
+
observability?: VoiceCampaignObservabilityOptions;
|
|
157
158
|
name?: string;
|
|
158
159
|
path?: string;
|
|
159
160
|
title?: string;
|
|
@@ -192,8 +193,67 @@ export type VoiceCampaignWorkerLoop = {
|
|
|
192
193
|
stop: () => void;
|
|
193
194
|
tick: () => Promise<VoiceCampaignWorkerResult>;
|
|
194
195
|
};
|
|
196
|
+
export type VoiceCampaignObservabilityOptions = {
|
|
197
|
+
leases?: VoiceRedisTaskLeaseCoordinator;
|
|
198
|
+
now?: number;
|
|
199
|
+
rateWindowMs?: number;
|
|
200
|
+
stuckAfterMs?: number;
|
|
201
|
+
};
|
|
202
|
+
export type VoiceCampaignObservabilityReport = {
|
|
203
|
+
attemptRate: {
|
|
204
|
+
failed: number;
|
|
205
|
+
started: number;
|
|
206
|
+
succeeded: number;
|
|
207
|
+
windowMs: number;
|
|
208
|
+
};
|
|
209
|
+
campaigns: Array<{
|
|
210
|
+
activeAttempts: number;
|
|
211
|
+
campaignId: string;
|
|
212
|
+
lease?: {
|
|
213
|
+
expiresAt: number;
|
|
214
|
+
workerId: string;
|
|
215
|
+
};
|
|
216
|
+
name: string;
|
|
217
|
+
queueDepth: number;
|
|
218
|
+
status: VoiceCampaignStatus;
|
|
219
|
+
stuckAttempts: number;
|
|
220
|
+
stuckRecipients: number;
|
|
221
|
+
updatedAt: number;
|
|
222
|
+
}>;
|
|
223
|
+
failureReasons: Array<{
|
|
224
|
+
count: number;
|
|
225
|
+
reason: string;
|
|
226
|
+
}>;
|
|
227
|
+
generatedAt: number;
|
|
228
|
+
leases: {
|
|
229
|
+
active: number;
|
|
230
|
+
known: boolean;
|
|
231
|
+
};
|
|
232
|
+
queue: {
|
|
233
|
+
activeAttempts: number;
|
|
234
|
+
queuedRecipients: number;
|
|
235
|
+
runningCampaigns: number;
|
|
236
|
+
};
|
|
237
|
+
stuck: {
|
|
238
|
+
attempts: Array<{
|
|
239
|
+
attemptId: string;
|
|
240
|
+
campaignId: string;
|
|
241
|
+
recipientId: string;
|
|
242
|
+
status: VoiceCampaignAttemptStatus;
|
|
243
|
+
updatedAt: number;
|
|
244
|
+
}>;
|
|
245
|
+
recipients: Array<{
|
|
246
|
+
campaignId: string;
|
|
247
|
+
recipientId: string;
|
|
248
|
+
status: VoiceCampaignRecipientStatus;
|
|
249
|
+
updatedAt: number;
|
|
250
|
+
}>;
|
|
251
|
+
};
|
|
252
|
+
summary: VoiceCampaignSummary;
|
|
253
|
+
};
|
|
195
254
|
export declare const createVoiceMemoryCampaignStore: () => VoiceCampaignStore;
|
|
196
255
|
export declare const summarizeVoiceCampaigns: (records: VoiceCampaignRecord[]) => VoiceCampaignSummary;
|
|
256
|
+
export declare const buildVoiceCampaignObservabilityReport: (records: VoiceCampaignRecord[], options?: VoiceCampaignObservabilityOptions) => Promise<VoiceCampaignObservabilityReport>;
|
|
197
257
|
export declare const createVoiceCampaign: (options: VoiceCampaignRuntimeOptions) => VoiceCampaignRuntime;
|
|
198
258
|
export declare const createVoiceCampaignWorker: (options: VoiceCampaignWorkerOptions) => VoiceCampaignWorker;
|
|
199
259
|
export declare const createVoiceCampaignWorkerLoop: (options: VoiceCampaignWorkerLoopOptions) => VoiceCampaignWorkerLoop;
|
|
@@ -201,6 +261,9 @@ export declare const runVoiceCampaignProof: (options?: VoiceCampaignProofOptions
|
|
|
201
261
|
export declare const renderVoiceCampaignsHTML: (records: VoiceCampaignRecord[], options?: {
|
|
202
262
|
title?: string;
|
|
203
263
|
}) => string;
|
|
264
|
+
export declare const renderVoiceCampaignObservabilityHTML: (report: VoiceCampaignObservabilityReport, options?: {
|
|
265
|
+
title?: string;
|
|
266
|
+
}) => string;
|
|
204
267
|
export declare const createVoiceCampaignRoutes: (options: VoiceCampaignRoutesOptions) => Elysia<"", {
|
|
205
268
|
decorator: {};
|
|
206
269
|
store: {};
|
|
@@ -231,6 +294,20 @@ export declare const createVoiceCampaignRoutes: (options: VoiceCampaignRoutesOpt
|
|
|
231
294
|
};
|
|
232
295
|
};
|
|
233
296
|
};
|
|
297
|
+
} & {
|
|
298
|
+
[x: string]: {
|
|
299
|
+
observability: {
|
|
300
|
+
get: {
|
|
301
|
+
body: unknown;
|
|
302
|
+
params: {};
|
|
303
|
+
query: unknown;
|
|
304
|
+
headers: unknown;
|
|
305
|
+
response: {
|
|
306
|
+
200: VoiceCampaignObservabilityReport;
|
|
307
|
+
};
|
|
308
|
+
};
|
|
309
|
+
};
|
|
310
|
+
};
|
|
234
311
|
} & {
|
|
235
312
|
[x: string]: {
|
|
236
313
|
post: {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { voice } from './plugin';
|
|
2
2
|
export { createVoiceAppKit, createVoiceAppKitRoutes, summarizeVoiceAppKitStatus } from './appKit';
|
|
3
|
-
export { createVoiceCampaign, createVoiceCampaignRoutes, createVoiceCampaignWorker, createVoiceCampaignWorkerLoop, createVoiceMemoryCampaignStore, renderVoiceCampaignsHTML, runVoiceCampaignProof, summarizeVoiceCampaigns } from './campaign';
|
|
3
|
+
export { buildVoiceCampaignObservabilityReport, createVoiceCampaign, createVoiceCampaignRoutes, createVoiceCampaignWorker, createVoiceCampaignWorkerLoop, createVoiceMemoryCampaignStore, renderVoiceCampaignObservabilityHTML, renderVoiceCampaignsHTML, runVoiceCampaignProof, summarizeVoiceCampaigns } from './campaign';
|
|
4
4
|
export { createVoiceAssistant, createVoiceExperiment, summarizeVoiceAssistantRuns } from './assistant';
|
|
5
5
|
export { createVoiceAssistantHealthHTMLHandler, createVoiceAssistantHealthJSONHandler, createVoiceAssistantHealthRoutes, renderVoiceAssistantHealthHTML, summarizeVoiceAssistantHealth } from './assistantHealth';
|
|
6
6
|
export { createVoiceBargeInRoutes, renderVoiceBargeInHTML, summarizeVoiceBargeIn } from './bargeInRoutes';
|
package/dist/index.js
CHANGED
|
@@ -10628,6 +10628,112 @@ var summarizeVoiceCampaigns = (records) => {
|
|
|
10628
10628
|
}
|
|
10629
10629
|
return summary;
|
|
10630
10630
|
};
|
|
10631
|
+
var buildVoiceCampaignObservabilityReport = async (records, options = {}) => {
|
|
10632
|
+
const now = options.now ?? Date.now();
|
|
10633
|
+
const stuckAfterMs = Math.max(1, options.stuckAfterMs ?? 15 * 60000);
|
|
10634
|
+
const rateWindowMs = Math.max(1, options.rateWindowMs ?? 60 * 60000);
|
|
10635
|
+
const rateWindowStart = now - rateWindowMs;
|
|
10636
|
+
const failureReasons = new Map;
|
|
10637
|
+
const report = {
|
|
10638
|
+
attemptRate: {
|
|
10639
|
+
failed: 0,
|
|
10640
|
+
started: 0,
|
|
10641
|
+
succeeded: 0,
|
|
10642
|
+
windowMs: rateWindowMs
|
|
10643
|
+
},
|
|
10644
|
+
campaigns: [],
|
|
10645
|
+
failureReasons: [],
|
|
10646
|
+
generatedAt: now,
|
|
10647
|
+
leases: {
|
|
10648
|
+
active: 0,
|
|
10649
|
+
known: Boolean(options.leases)
|
|
10650
|
+
},
|
|
10651
|
+
queue: {
|
|
10652
|
+
activeAttempts: 0,
|
|
10653
|
+
queuedRecipients: 0,
|
|
10654
|
+
runningCampaigns: 0
|
|
10655
|
+
},
|
|
10656
|
+
stuck: {
|
|
10657
|
+
attempts: [],
|
|
10658
|
+
recipients: []
|
|
10659
|
+
},
|
|
10660
|
+
summary: summarizeVoiceCampaigns(records)
|
|
10661
|
+
};
|
|
10662
|
+
for (const record of records) {
|
|
10663
|
+
const campaignId = record.campaign.id;
|
|
10664
|
+
const queuedRecipients = record.recipients.filter((recipient) => recipient.status === "queued");
|
|
10665
|
+
const activeAttempts = record.attempts.filter((attempt) => attempt.status === "queued" || attempt.status === "running");
|
|
10666
|
+
const campaignReport = {
|
|
10667
|
+
activeAttempts: activeAttempts.length,
|
|
10668
|
+
campaignId,
|
|
10669
|
+
name: record.campaign.name,
|
|
10670
|
+
queueDepth: queuedRecipients.length,
|
|
10671
|
+
status: record.campaign.status,
|
|
10672
|
+
stuckAttempts: 0,
|
|
10673
|
+
stuckRecipients: 0,
|
|
10674
|
+
updatedAt: record.campaign.updatedAt
|
|
10675
|
+
};
|
|
10676
|
+
if (record.campaign.status === "running") {
|
|
10677
|
+
report.queue.runningCampaigns += 1;
|
|
10678
|
+
}
|
|
10679
|
+
report.queue.queuedRecipients += queuedRecipients.length;
|
|
10680
|
+
report.queue.activeAttempts += activeAttempts.length;
|
|
10681
|
+
for (const recipient of record.recipients) {
|
|
10682
|
+
if ((recipient.status === "pending" || recipient.status === "queued") && now - recipient.updatedAt >= stuckAfterMs) {
|
|
10683
|
+
campaignReport.stuckRecipients += 1;
|
|
10684
|
+
report.stuck.recipients.push({
|
|
10685
|
+
campaignId,
|
|
10686
|
+
recipientId: recipient.id,
|
|
10687
|
+
status: recipient.status,
|
|
10688
|
+
updatedAt: recipient.updatedAt
|
|
10689
|
+
});
|
|
10690
|
+
}
|
|
10691
|
+
if (recipient.error) {
|
|
10692
|
+
failureReasons.set(recipient.error, (failureReasons.get(recipient.error) ?? 0) + 1);
|
|
10693
|
+
}
|
|
10694
|
+
}
|
|
10695
|
+
for (const attempt of record.attempts) {
|
|
10696
|
+
if ((attempt.startedAt ?? attempt.createdAt) >= rateWindowStart) {
|
|
10697
|
+
report.attemptRate.started += 1;
|
|
10698
|
+
}
|
|
10699
|
+
if (attempt.status === "failed" && attempt.updatedAt >= rateWindowStart) {
|
|
10700
|
+
report.attemptRate.failed += 1;
|
|
10701
|
+
}
|
|
10702
|
+
if (attempt.status === "succeeded" && attempt.updatedAt >= rateWindowStart) {
|
|
10703
|
+
report.attemptRate.succeeded += 1;
|
|
10704
|
+
}
|
|
10705
|
+
if (attempt.error) {
|
|
10706
|
+
failureReasons.set(attempt.error, (failureReasons.get(attempt.error) ?? 0) + 1);
|
|
10707
|
+
}
|
|
10708
|
+
if ((attempt.status === "queued" || attempt.status === "running") && now - attempt.updatedAt >= stuckAfterMs) {
|
|
10709
|
+
campaignReport.stuckAttempts += 1;
|
|
10710
|
+
report.stuck.attempts.push({
|
|
10711
|
+
attemptId: attempt.id,
|
|
10712
|
+
campaignId,
|
|
10713
|
+
recipientId: attempt.recipientId,
|
|
10714
|
+
status: attempt.status,
|
|
10715
|
+
updatedAt: attempt.updatedAt
|
|
10716
|
+
});
|
|
10717
|
+
}
|
|
10718
|
+
}
|
|
10719
|
+
if (options.leases) {
|
|
10720
|
+
const lease = await options.leases.get(getCampaignLeaseTaskId(campaignId));
|
|
10721
|
+
if (lease) {
|
|
10722
|
+
report.leases.active += 1;
|
|
10723
|
+
campaignReport.lease = {
|
|
10724
|
+
expiresAt: lease.expiresAt,
|
|
10725
|
+
workerId: lease.workerId
|
|
10726
|
+
};
|
|
10727
|
+
}
|
|
10728
|
+
}
|
|
10729
|
+
report.campaigns.push(campaignReport);
|
|
10730
|
+
}
|
|
10731
|
+
report.failureReasons = [...failureReasons.entries()].map(([reason, count]) => ({ count, reason })).sort((left, right) => right.count === left.count ? left.reason.localeCompare(right.reason) : right.count - left.count);
|
|
10732
|
+
report.campaigns.sort((left, right) => right.updatedAt - left.updatedAt);
|
|
10733
|
+
report.stuck.attempts.sort((left, right) => left.updatedAt - right.updatedAt);
|
|
10734
|
+
report.stuck.recipients.sort((left, right) => left.updatedAt - right.updatedAt);
|
|
10735
|
+
return report;
|
|
10736
|
+
};
|
|
10631
10737
|
var createVoiceCampaign = (options) => {
|
|
10632
10738
|
const { store } = options;
|
|
10633
10739
|
return {
|
|
@@ -10953,6 +11059,12 @@ var renderVoiceCampaignsHTML = (records, options = {}) => {
|
|
|
10953
11059
|
const summary = summarizeVoiceCampaigns(records);
|
|
10954
11060
|
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>`;
|
|
10955
11061
|
};
|
|
11062
|
+
var renderVoiceCampaignObservabilityHTML = (report, options = {}) => {
|
|
11063
|
+
const title = options.title ?? "Voice Campaign Observability";
|
|
11064
|
+
const campaignRows = report.campaigns.map((campaign) => `<tr><td>${escapeHtml18(campaign.name)}</td><td>${escapeHtml18(campaign.status)}</td><td>${String(campaign.queueDepth)}</td><td>${String(campaign.activeAttempts)}</td><td>${String(campaign.stuckRecipients + campaign.stuckAttempts)}</td><td>${campaign.lease ? escapeHtml18(campaign.lease.workerId) : "none"}</td></tr>`).join("");
|
|
11065
|
+
const failureRows = report.failureReasons.map((failure) => `<tr><td>${escapeHtml18(failure.reason)}</td><td>${String(failure.count)}</td></tr>`).join("");
|
|
11066
|
+
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:#0b1220;color:#e5edf7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.2),rgba(251,146,60,.14));border:1px solid #334155;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.2rem,5vw,4.6rem);line-height:.95;margin:.2rem 0 1rem}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));margin:18px 0}.card,table{background:#111c2f;border:1px solid #334155;border-radius:18px}.card{padding:16px}.card span{color:#9fb0c5}.card strong{display:block;font-size:2rem;margin:.25rem 0}table{border-collapse:collapse;margin-top:18px;overflow:hidden;width:100%}td,th{border-bottom:1px solid #334155;padding:12px;text-align:left}.warn{color:#fde68a}.bad{color:#fecaca}</style></head><body><main><section class="hero"><p class="eyebrow">Campaign ops</p><h1>${escapeHtml18(title)}</h1><p>Queue depth, active leases, attempt rates, failure reasons, and stuck work for self-hosted outbound voice.</p><section class="grid"><article class="card"><span>Queued recipients</span><strong>${String(report.queue.queuedRecipients)}</strong></article><article class="card"><span>Active attempts</span><strong>${String(report.queue.activeAttempts)}</strong></article><article class="card"><span>Running campaigns</span><strong>${String(report.queue.runningCampaigns)}</strong></article><article class="card"><span>Active leases</span><strong>${report.leases.known ? String(report.leases.active) : "n/a"}</strong></article><article class="card"><span>Attempts/window</span><strong>${String(report.attemptRate.started)}</strong></article><article class="card"><span>Stuck work</span><strong class="${report.stuck.attempts.length + report.stuck.recipients.length > 0 ? "bad" : ""}">${String(report.stuck.attempts.length + report.stuck.recipients.length)}</strong></article></section></section><h2>Campaigns</h2><table><thead><tr><th>Name</th><th>Status</th><th>Queued</th><th>Active</th><th>Stuck</th><th>Lease</th></tr></thead><tbody>${campaignRows || '<tr><td colspan="6">No campaigns yet.</td></tr>'}</tbody></table><h2>Failure Reasons</h2><table><thead><tr><th>Reason</th><th>Count</th></tr></thead><tbody>${failureRows || '<tr><td colspan="2">No failures recorded.</td></tr>'}</tbody></table></main></body></html>`;
|
|
11067
|
+
};
|
|
10956
11068
|
var readJsonBody = async (request) => {
|
|
10957
11069
|
const text = await request.text();
|
|
10958
11070
|
return text.trim() ? JSON.parse(text) : {};
|
|
@@ -10964,7 +11076,7 @@ var createVoiceCampaignRoutes = (options) => {
|
|
|
10964
11076
|
const app = new Elysia17({ name: options.name ?? "absolutejs-voice-campaigns" }).get(path, async () => ({
|
|
10965
11077
|
campaigns: await runtime.list(),
|
|
10966
11078
|
summary: await runtime.summarize()
|
|
10967
|
-
})).post(path, async ({ request }) => runtime.create(await readJsonBody(request))).get(`${path}/:campaignId`, ({ params }) => runtime.get(params.campaignId)).delete(`${path}/:campaignId`, async ({ params }) => {
|
|
11079
|
+
})).get(`${path}/observability`, async () => buildVoiceCampaignObservabilityReport(await runtime.list(), options.observability)).post(path, async ({ request }) => runtime.create(await readJsonBody(request))).get(`${path}/:campaignId`, ({ params }) => runtime.get(params.campaignId)).delete(`${path}/:campaignId`, async ({ params }) => {
|
|
10968
11080
|
await runtime.remove(params.campaignId);
|
|
10969
11081
|
return { ok: true };
|
|
10970
11082
|
}).post(`${path}/:campaignId/recipients`, async ({ params, request }) => {
|
|
@@ -10983,6 +11095,14 @@ var createVoiceCampaignRoutes = (options) => {
|
|
|
10983
11095
|
...options.headers
|
|
10984
11096
|
}
|
|
10985
11097
|
});
|
|
11098
|
+
}).get(`${htmlPath}/observability`, async () => {
|
|
11099
|
+
const report = await buildVoiceCampaignObservabilityReport(await runtime.list(), options.observability);
|
|
11100
|
+
return new Response(renderVoiceCampaignObservabilityHTML(report, options), {
|
|
11101
|
+
headers: {
|
|
11102
|
+
"content-type": "text/html; charset=utf-8",
|
|
11103
|
+
...options.headers
|
|
11104
|
+
}
|
|
11105
|
+
});
|
|
10986
11106
|
});
|
|
10987
11107
|
}
|
|
10988
11108
|
return app;
|
|
@@ -19033,6 +19153,7 @@ export {
|
|
|
19033
19153
|
renderVoiceEvalHTML,
|
|
19034
19154
|
renderVoiceEvalBaselineHTML,
|
|
19035
19155
|
renderVoiceCampaignsHTML,
|
|
19156
|
+
renderVoiceCampaignObservabilityHTML,
|
|
19036
19157
|
renderVoiceCallReviewMarkdown,
|
|
19037
19158
|
renderVoiceCallReviewHTML,
|
|
19038
19159
|
renderVoiceBargeInHTML,
|
|
@@ -19262,6 +19383,7 @@ export {
|
|
|
19262
19383
|
buildVoiceOpsTaskFromReview,
|
|
19263
19384
|
buildVoiceOpsConsoleReport,
|
|
19264
19385
|
buildVoiceDiagnosticsMarkdown,
|
|
19386
|
+
buildVoiceCampaignObservabilityReport,
|
|
19265
19387
|
assignVoiceOpsTask,
|
|
19266
19388
|
applyVoiceTelephonyOutcome,
|
|
19267
19389
|
applyVoiceOpsTaskPolicy,
|