@absolutejs/voice 0.0.22-beta.510 → 0.0.22-beta.512
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/angular/index.js +13 -0
- package/dist/bookingFlow.d.ts +43 -0
- package/dist/calendarAdapter.d.ts +47 -0
- package/dist/calendarSlots.d.ts +35 -0
- package/dist/client/index.js +13 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +930 -0
- package/dist/liveCoach.d.ts +43 -0
- package/dist/noShowPredictor.d.ts +46 -0
- package/dist/react/index.js +13 -0
- package/dist/reminderScheduler.d.ts +43 -0
- package/dist/supervisorPermissions.d.ts +33 -0
- package/dist/supervisorPresence.d.ts +49 -0
- package/dist/svelte/index.js +13 -0
- package/dist/testing/index.js +13 -0
- package/dist/transcriptAnnotator.d.ts +41 -0
- package/dist/vue/index.js +13 -0
- package/dist/whisperChannel.d.ts +50 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,6 +10,19 @@ var __name = (target, name) => {
|
|
|
10
10
|
});
|
|
11
11
|
return target;
|
|
12
12
|
};
|
|
13
|
+
var __returnValue = (v) => v;
|
|
14
|
+
function __exportSetter(name, newValue) {
|
|
15
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
16
|
+
}
|
|
17
|
+
var __export = (target, all) => {
|
|
18
|
+
for (var name in all)
|
|
19
|
+
__defProp(target, name, {
|
|
20
|
+
get: all[name],
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
set: __exportSetter.bind(all, name)
|
|
24
|
+
});
|
|
25
|
+
};
|
|
13
26
|
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
14
27
|
var __typeError = (msg) => {
|
|
15
28
|
throw TypeError(msg);
|
|
@@ -70,6 +83,115 @@ var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
|
70
83
|
};
|
|
71
84
|
var __require = import.meta.require;
|
|
72
85
|
|
|
86
|
+
// src/calendarSlots.ts
|
|
87
|
+
var exports_calendarSlots = {};
|
|
88
|
+
__export(exports_calendarSlots, {
|
|
89
|
+
summarizeVoiceCalendarSlot: () => summarizeVoiceCalendarSlot,
|
|
90
|
+
generateVoiceCalendarSlots: () => generateVoiceCalendarSlots
|
|
91
|
+
});
|
|
92
|
+
var parseHHMM = (value) => {
|
|
93
|
+
const match = /^([0-9]{1,2}):([0-9]{2})$/u.exec(value);
|
|
94
|
+
if (!match)
|
|
95
|
+
throw new Error(`Invalid time string (expected HH:MM): ${value}`);
|
|
96
|
+
return Number(match[1]) * 60 + Number(match[2]);
|
|
97
|
+
}, partsAt = (ms, timezone) => {
|
|
98
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
99
|
+
day: "2-digit",
|
|
100
|
+
hour: "2-digit",
|
|
101
|
+
hour12: false,
|
|
102
|
+
minute: "2-digit",
|
|
103
|
+
month: "2-digit",
|
|
104
|
+
timeZone: timezone,
|
|
105
|
+
weekday: "short",
|
|
106
|
+
year: "numeric"
|
|
107
|
+
});
|
|
108
|
+
const map = {};
|
|
109
|
+
for (const part of formatter.formatToParts(new Date(ms))) {
|
|
110
|
+
if (part.type !== "literal")
|
|
111
|
+
map[part.type] = part.value;
|
|
112
|
+
}
|
|
113
|
+
const weekdayMap = {
|
|
114
|
+
Fri: 5,
|
|
115
|
+
Mon: 1,
|
|
116
|
+
Sat: 6,
|
|
117
|
+
Sun: 0,
|
|
118
|
+
Thu: 4,
|
|
119
|
+
Tue: 2,
|
|
120
|
+
Wed: 3
|
|
121
|
+
};
|
|
122
|
+
const hourValue = map.hour === "24" ? "00" : map.hour ?? "0";
|
|
123
|
+
return {
|
|
124
|
+
date: `${map.year}-${map.month}-${map.day}`,
|
|
125
|
+
minutes: Number(hourValue) * 60 + Number(map.minute ?? "0"),
|
|
126
|
+
weekday: weekdayMap[map.weekday ?? ""] ?? 0
|
|
127
|
+
};
|
|
128
|
+
}, overlaps = (aStart, aEnd, bStart, bEnd) => aStart < bEnd && bStart < aEnd, generateVoiceCalendarSlots = (input) => {
|
|
129
|
+
if (input.durationMinutes <= 0) {
|
|
130
|
+
throw new Error("durationMinutes must be positive");
|
|
131
|
+
}
|
|
132
|
+
if (input.toMs <= input.fromMs)
|
|
133
|
+
return [];
|
|
134
|
+
const granularity = input.granularityMinutes ?? 15;
|
|
135
|
+
const buffer = input.bufferMinutes ?? 0;
|
|
136
|
+
const max = input.maxSlots ?? Infinity;
|
|
137
|
+
const hoursByDay = new Map;
|
|
138
|
+
for (const block of input.businessHours) {
|
|
139
|
+
const list = hoursByDay.get(block.weekday) ?? [];
|
|
140
|
+
list.push(block);
|
|
141
|
+
hoursByDay.set(block.weekday, list);
|
|
142
|
+
}
|
|
143
|
+
const blackoutDates = new Set((input.blackoutDates ?? []).map((b) => b.date));
|
|
144
|
+
const slots = [];
|
|
145
|
+
const stepMs = granularity * 60000;
|
|
146
|
+
const durationMs = input.durationMinutes * 60000;
|
|
147
|
+
const bufferMs = buffer * 60000;
|
|
148
|
+
let cursor = input.fromMs;
|
|
149
|
+
while (cursor + durationMs <= input.toMs && slots.length < max) {
|
|
150
|
+
const slotEnd = cursor + durationMs;
|
|
151
|
+
const startParts = partsAt(cursor, input.timezone);
|
|
152
|
+
if (blackoutDates.has(startParts.date)) {
|
|
153
|
+
cursor += stepMs;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const dayHours = hoursByDay.get(startParts.weekday);
|
|
157
|
+
if (!dayHours || dayHours.length === 0) {
|
|
158
|
+
cursor += stepMs;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const endParts = partsAt(slotEnd - 1, input.timezone);
|
|
162
|
+
const fitsHours = dayHours.some((block) => {
|
|
163
|
+
const startMin = parseHHMM(block.start);
|
|
164
|
+
const endMin = parseHHMM(block.end);
|
|
165
|
+
return startParts.minutes >= startMin && endParts.minutes < endMin && startParts.date === endParts.date;
|
|
166
|
+
});
|
|
167
|
+
if (!fitsHours) {
|
|
168
|
+
cursor += stepMs;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const collides = (input.bookedRanges ?? []).some((booked) => overlaps(cursor - bufferMs, slotEnd + bufferMs, booked.startMs, booked.endMs));
|
|
172
|
+
if (!collides) {
|
|
173
|
+
slots.push({
|
|
174
|
+
durationMinutes: input.durationMinutes,
|
|
175
|
+
endMs: slotEnd,
|
|
176
|
+
startMs: cursor
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
cursor += stepMs;
|
|
180
|
+
}
|
|
181
|
+
return slots;
|
|
182
|
+
}, summarizeVoiceCalendarSlot = (slot, options = {}) => {
|
|
183
|
+
const formatter = new Intl.DateTimeFormat(options.locale ?? "en-US", {
|
|
184
|
+
day: "numeric",
|
|
185
|
+
hour: "numeric",
|
|
186
|
+
hour12: true,
|
|
187
|
+
minute: "2-digit",
|
|
188
|
+
month: "long",
|
|
189
|
+
timeZone: options.timezone,
|
|
190
|
+
weekday: "long"
|
|
191
|
+
});
|
|
192
|
+
return formatter.format(new Date(slot.startMs));
|
|
193
|
+
};
|
|
194
|
+
|
|
73
195
|
// src/audioConditioning.ts
|
|
74
196
|
var DEFAULT_TARGET_LEVEL = 0.08;
|
|
75
197
|
var DEFAULT_MAX_GAIN = 3;
|
|
@@ -49171,6 +49293,799 @@ var collectVoiceCampaignTemplateVariables = (template) => {
|
|
|
49171
49293
|
}
|
|
49172
49294
|
return Array.from(set);
|
|
49173
49295
|
};
|
|
49296
|
+
// src/whisperChannel.ts
|
|
49297
|
+
var createVoiceWhisperChannel = (options) => {
|
|
49298
|
+
const now = options.now ?? (() => Date.now());
|
|
49299
|
+
const defaultRoute = options.defaultRoute ?? "agent-only";
|
|
49300
|
+
const duckLevel = options.duckCallerToLevel ?? 0.25;
|
|
49301
|
+
const maxConcurrent = options.maxConcurrentWhispers ?? 1;
|
|
49302
|
+
const active = new Map;
|
|
49303
|
+
const listeners = new Set;
|
|
49304
|
+
const broadcast = (event) => {
|
|
49305
|
+
for (const listener of listeners)
|
|
49306
|
+
listener(event);
|
|
49307
|
+
};
|
|
49308
|
+
const start = (supervisorId, route = defaultRoute) => {
|
|
49309
|
+
if (active.has(supervisorId))
|
|
49310
|
+
return active.get(supervisorId);
|
|
49311
|
+
if (active.size >= maxConcurrent) {
|
|
49312
|
+
throw new Error(`Whisper channel already at max concurrent (${maxConcurrent})`);
|
|
49313
|
+
}
|
|
49314
|
+
const entry = {
|
|
49315
|
+
route,
|
|
49316
|
+
startedAt: now(),
|
|
49317
|
+
supervisorId
|
|
49318
|
+
};
|
|
49319
|
+
active.set(supervisorId, entry);
|
|
49320
|
+
broadcast({ at: entry.startedAt, supervisorId, type: "started" });
|
|
49321
|
+
if (route === "agent-only") {
|
|
49322
|
+
broadcast({
|
|
49323
|
+
at: entry.startedAt,
|
|
49324
|
+
level: duckLevel,
|
|
49325
|
+
supervisorId,
|
|
49326
|
+
type: "ducked"
|
|
49327
|
+
});
|
|
49328
|
+
}
|
|
49329
|
+
return entry;
|
|
49330
|
+
};
|
|
49331
|
+
const stop = (supervisorId) => {
|
|
49332
|
+
if (!active.has(supervisorId))
|
|
49333
|
+
return false;
|
|
49334
|
+
active.delete(supervisorId);
|
|
49335
|
+
broadcast({ at: now(), supervisorId, type: "stopped" });
|
|
49336
|
+
return true;
|
|
49337
|
+
};
|
|
49338
|
+
const pushFrame = (frame) => {
|
|
49339
|
+
const entry = active.get(frame.supervisorId);
|
|
49340
|
+
if (!entry)
|
|
49341
|
+
return "drop";
|
|
49342
|
+
if (entry.route === "drop")
|
|
49343
|
+
return "drop";
|
|
49344
|
+
broadcast({ frame, type: "frame" });
|
|
49345
|
+
return entry.route;
|
|
49346
|
+
};
|
|
49347
|
+
return {
|
|
49348
|
+
activeSupervisors: () => Array.from(active.keys()),
|
|
49349
|
+
isWhispering: (supervisorId) => active.has(supervisorId),
|
|
49350
|
+
pushFrame,
|
|
49351
|
+
routeFor: (supervisorId) => active.get(supervisorId)?.route ?? null,
|
|
49352
|
+
sessionId: options.sessionId,
|
|
49353
|
+
setRoute(supervisorId, route) {
|
|
49354
|
+
const entry = active.get(supervisorId);
|
|
49355
|
+
if (!entry)
|
|
49356
|
+
return false;
|
|
49357
|
+
entry.route = route;
|
|
49358
|
+
return true;
|
|
49359
|
+
},
|
|
49360
|
+
start,
|
|
49361
|
+
stop,
|
|
49362
|
+
subscribe(listener) {
|
|
49363
|
+
listeners.add(listener);
|
|
49364
|
+
return () => {
|
|
49365
|
+
listeners.delete(listener);
|
|
49366
|
+
};
|
|
49367
|
+
}
|
|
49368
|
+
};
|
|
49369
|
+
};
|
|
49370
|
+
// src/liveCoach.ts
|
|
49371
|
+
var DEFAULT_TEMPLATES = {
|
|
49372
|
+
correction: "Supervisor correction (do not repeat this verbatim; weave it into your next response): {{text}}",
|
|
49373
|
+
hint: "Supervisor hint: {{text}}",
|
|
49374
|
+
knowledge: "Reference information from supervisor: {{text}}",
|
|
49375
|
+
"script-line": "Supervisor-approved phrasing to use next: {{text}}",
|
|
49376
|
+
warning: "Supervisor warning: {{text}}. Adjust your approach."
|
|
49377
|
+
};
|
|
49378
|
+
var createVoiceLiveCoach = (options) => {
|
|
49379
|
+
const now = options.now ?? (() => Date.now());
|
|
49380
|
+
const generateId = options.generateId ?? (() => `nudge_${Math.random().toString(36).slice(2, 10)}`);
|
|
49381
|
+
const role = options.injectionRole ?? "system";
|
|
49382
|
+
const templates = { ...DEFAULT_TEMPLATES, ...options.templateForKind ?? {} };
|
|
49383
|
+
const nudges = [];
|
|
49384
|
+
const listeners = new Set;
|
|
49385
|
+
const push = (input) => {
|
|
49386
|
+
const nudge = {
|
|
49387
|
+
acknowledged: false,
|
|
49388
|
+
createdAt: now(),
|
|
49389
|
+
id: input.id ?? generateId(),
|
|
49390
|
+
injected: false,
|
|
49391
|
+
kind: input.kind,
|
|
49392
|
+
sessionId: options.sessionId,
|
|
49393
|
+
supervisorId: input.supervisorId,
|
|
49394
|
+
text: input.text,
|
|
49395
|
+
...input.expiresAt !== undefined ? { expiresAt: input.expiresAt } : options.defaultExpiryMs !== undefined ? { expiresAt: now() + options.defaultExpiryMs } : {}
|
|
49396
|
+
};
|
|
49397
|
+
nudges.push(nudge);
|
|
49398
|
+
for (const listener of listeners)
|
|
49399
|
+
listener(nudge);
|
|
49400
|
+
return nudge;
|
|
49401
|
+
};
|
|
49402
|
+
const pending = () => {
|
|
49403
|
+
const at = now();
|
|
49404
|
+
return nudges.filter((n) => !n.injected && !n.acknowledged && (n.expiresAt === undefined || n.expiresAt > at));
|
|
49405
|
+
};
|
|
49406
|
+
const consumeForInjection = () => {
|
|
49407
|
+
const at = now();
|
|
49408
|
+
const ready = pending();
|
|
49409
|
+
const result = [];
|
|
49410
|
+
for (const nudge of ready) {
|
|
49411
|
+
const template = templates[nudge.kind] ?? DEFAULT_TEMPLATES[nudge.kind];
|
|
49412
|
+
const content = template.replace(/\{\{text\}\}/gu, nudge.text);
|
|
49413
|
+
nudge.injected = true;
|
|
49414
|
+
nudge.injectedAt = at;
|
|
49415
|
+
result.push({
|
|
49416
|
+
content,
|
|
49417
|
+
metadata: {
|
|
49418
|
+
kind: nudge.kind,
|
|
49419
|
+
nudgeId: nudge.id,
|
|
49420
|
+
supervisorId: nudge.supervisorId
|
|
49421
|
+
},
|
|
49422
|
+
role
|
|
49423
|
+
});
|
|
49424
|
+
}
|
|
49425
|
+
return result;
|
|
49426
|
+
};
|
|
49427
|
+
const acknowledge = (id) => {
|
|
49428
|
+
const nudge = nudges.find((n) => n.id === id);
|
|
49429
|
+
if (!nudge)
|
|
49430
|
+
return false;
|
|
49431
|
+
nudge.acknowledged = true;
|
|
49432
|
+
nudge.acknowledgedAt = now();
|
|
49433
|
+
return true;
|
|
49434
|
+
};
|
|
49435
|
+
return {
|
|
49436
|
+
acknowledge,
|
|
49437
|
+
consumeForInjection,
|
|
49438
|
+
history: () => nudges.slice(),
|
|
49439
|
+
pending,
|
|
49440
|
+
push,
|
|
49441
|
+
sessionId: options.sessionId,
|
|
49442
|
+
subscribe(listener) {
|
|
49443
|
+
listeners.add(listener);
|
|
49444
|
+
return () => {
|
|
49445
|
+
listeners.delete(listener);
|
|
49446
|
+
};
|
|
49447
|
+
}
|
|
49448
|
+
};
|
|
49449
|
+
};
|
|
49450
|
+
// src/transcriptAnnotator.ts
|
|
49451
|
+
var DEFAULT_VOICE_ANNOTATION_KIND_SEVERITY = {
|
|
49452
|
+
"compliance-concern": "major",
|
|
49453
|
+
custom: "info",
|
|
49454
|
+
"follow-up-needed": "minor",
|
|
49455
|
+
"great-recovery": "info",
|
|
49456
|
+
"knowledge-gap": "minor",
|
|
49457
|
+
"missed-objection": "minor",
|
|
49458
|
+
"tone-issue": "minor"
|
|
49459
|
+
};
|
|
49460
|
+
var createVoiceTranscriptAnnotator = (options) => {
|
|
49461
|
+
const now = options.now ?? (() => Date.now());
|
|
49462
|
+
const generateId = options.generateId ?? (() => `ann_${Math.random().toString(36).slice(2, 10)}`);
|
|
49463
|
+
const annotations = [];
|
|
49464
|
+
const add = (input) => {
|
|
49465
|
+
if (input.kind === "custom" && !input.customLabel) {
|
|
49466
|
+
throw new Error("customLabel is required for kind=custom");
|
|
49467
|
+
}
|
|
49468
|
+
const annotation = {
|
|
49469
|
+
createdAt: now(),
|
|
49470
|
+
id: input.id ?? generateId(),
|
|
49471
|
+
kind: input.kind,
|
|
49472
|
+
rangeStartMs: input.rangeStartMs,
|
|
49473
|
+
sessionId: options.sessionId,
|
|
49474
|
+
severity: input.severity ?? DEFAULT_VOICE_ANNOTATION_KIND_SEVERITY[input.kind],
|
|
49475
|
+
supervisorId: input.supervisorId,
|
|
49476
|
+
...input.customLabel !== undefined ? { customLabel: input.customLabel } : {},
|
|
49477
|
+
...input.rangeEndMs !== undefined ? { rangeEndMs: input.rangeEndMs } : {},
|
|
49478
|
+
...input.text !== undefined ? { text: input.text } : {},
|
|
49479
|
+
...input.turnId !== undefined ? { turnId: input.turnId } : {}
|
|
49480
|
+
};
|
|
49481
|
+
annotations.push(annotation);
|
|
49482
|
+
return annotation;
|
|
49483
|
+
};
|
|
49484
|
+
const remove = (id) => {
|
|
49485
|
+
const idx = annotations.findIndex((a) => a.id === id);
|
|
49486
|
+
if (idx === -1)
|
|
49487
|
+
return false;
|
|
49488
|
+
annotations.splice(idx, 1);
|
|
49489
|
+
return true;
|
|
49490
|
+
};
|
|
49491
|
+
const list = (filter) => annotations.filter((a) => {
|
|
49492
|
+
if (filter?.kind && a.kind !== filter.kind)
|
|
49493
|
+
return false;
|
|
49494
|
+
if (filter?.supervisorId && a.supervisorId !== filter.supervisorId) {
|
|
49495
|
+
return false;
|
|
49496
|
+
}
|
|
49497
|
+
if (filter?.severity && a.severity !== filter.severity)
|
|
49498
|
+
return false;
|
|
49499
|
+
if (filter?.fromMs !== undefined && a.rangeStartMs < filter.fromMs) {
|
|
49500
|
+
return false;
|
|
49501
|
+
}
|
|
49502
|
+
if (filter?.toMs !== undefined && a.rangeStartMs > filter.toMs) {
|
|
49503
|
+
return false;
|
|
49504
|
+
}
|
|
49505
|
+
return true;
|
|
49506
|
+
});
|
|
49507
|
+
const summarize = () => {
|
|
49508
|
+
const byKind = {};
|
|
49509
|
+
const bySeverity = {
|
|
49510
|
+
info: 0,
|
|
49511
|
+
major: 0,
|
|
49512
|
+
minor: 0
|
|
49513
|
+
};
|
|
49514
|
+
for (const a of annotations) {
|
|
49515
|
+
byKind[a.kind] = (byKind[a.kind] ?? 0) + 1;
|
|
49516
|
+
bySeverity[a.severity] += 1;
|
|
49517
|
+
}
|
|
49518
|
+
return { byKind, bySeverity, total: annotations.length };
|
|
49519
|
+
};
|
|
49520
|
+
return {
|
|
49521
|
+
add,
|
|
49522
|
+
list,
|
|
49523
|
+
remove,
|
|
49524
|
+
sessionId: options.sessionId,
|
|
49525
|
+
summarize
|
|
49526
|
+
};
|
|
49527
|
+
};
|
|
49528
|
+
// src/supervisorPresence.ts
|
|
49529
|
+
var createVoiceSupervisorPresence = (options = {}) => {
|
|
49530
|
+
const now = options.now ?? (() => Date.now());
|
|
49531
|
+
const staleAfter = options.staleAfterMs ?? 30000;
|
|
49532
|
+
const bySession = new Map;
|
|
49533
|
+
const listeners = new Set;
|
|
49534
|
+
const emit2 = (event) => {
|
|
49535
|
+
for (const listener of listeners)
|
|
49536
|
+
listener(event);
|
|
49537
|
+
};
|
|
49538
|
+
const ensureSession = (sessionId) => {
|
|
49539
|
+
let map = bySession.get(sessionId);
|
|
49540
|
+
if (!map) {
|
|
49541
|
+
map = new Map;
|
|
49542
|
+
bySession.set(sessionId, map);
|
|
49543
|
+
}
|
|
49544
|
+
return map;
|
|
49545
|
+
};
|
|
49546
|
+
const pruneStaleFromSession = (sessionId, sessionWatchers) => {
|
|
49547
|
+
const at = now();
|
|
49548
|
+
for (const [id, w] of sessionWatchers) {
|
|
49549
|
+
if (at - w.lastSeenAt > staleAfter) {
|
|
49550
|
+
sessionWatchers.delete(id);
|
|
49551
|
+
emit2({ at, sessionId, supervisorId: id, type: "leave" });
|
|
49552
|
+
}
|
|
49553
|
+
}
|
|
49554
|
+
};
|
|
49555
|
+
const join5 = (input) => {
|
|
49556
|
+
const sessionWatchers = ensureSession(input.sessionId);
|
|
49557
|
+
pruneStaleFromSession(input.sessionId, sessionWatchers);
|
|
49558
|
+
const at = now();
|
|
49559
|
+
const watcher = {
|
|
49560
|
+
joinedAt: at,
|
|
49561
|
+
lastSeenAt: at,
|
|
49562
|
+
role: input.role ?? "viewer",
|
|
49563
|
+
sessionId: input.sessionId,
|
|
49564
|
+
supervisorId: input.supervisorId,
|
|
49565
|
+
...input.displayName !== undefined ? { displayName: input.displayName } : {}
|
|
49566
|
+
};
|
|
49567
|
+
sessionWatchers.set(input.supervisorId, watcher);
|
|
49568
|
+
emit2({ type: "join", watcher });
|
|
49569
|
+
return watcher;
|
|
49570
|
+
};
|
|
49571
|
+
const leave = (sessionId, supervisorId) => {
|
|
49572
|
+
const sessionWatchers = bySession.get(sessionId);
|
|
49573
|
+
if (!sessionWatchers?.delete(supervisorId))
|
|
49574
|
+
return false;
|
|
49575
|
+
if (sessionWatchers.size === 0)
|
|
49576
|
+
bySession.delete(sessionId);
|
|
49577
|
+
emit2({ at: now(), sessionId, supervisorId, type: "leave" });
|
|
49578
|
+
return true;
|
|
49579
|
+
};
|
|
49580
|
+
const heartbeat = (sessionId, supervisorId) => {
|
|
49581
|
+
const watcher = bySession.get(sessionId)?.get(supervisorId);
|
|
49582
|
+
if (!watcher)
|
|
49583
|
+
return false;
|
|
49584
|
+
const at = now();
|
|
49585
|
+
watcher.lastSeenAt = at;
|
|
49586
|
+
emit2({ at, sessionId, supervisorId, type: "heartbeat" });
|
|
49587
|
+
return true;
|
|
49588
|
+
};
|
|
49589
|
+
const setRole = (sessionId, supervisorId, role) => {
|
|
49590
|
+
const watcher = bySession.get(sessionId)?.get(supervisorId);
|
|
49591
|
+
if (!watcher)
|
|
49592
|
+
return false;
|
|
49593
|
+
if (watcher.role === role)
|
|
49594
|
+
return true;
|
|
49595
|
+
const from = watcher.role;
|
|
49596
|
+
watcher.role = role;
|
|
49597
|
+
emit2({
|
|
49598
|
+
at: now(),
|
|
49599
|
+
from,
|
|
49600
|
+
sessionId,
|
|
49601
|
+
supervisorId,
|
|
49602
|
+
to: role,
|
|
49603
|
+
type: "role-change"
|
|
49604
|
+
});
|
|
49605
|
+
return true;
|
|
49606
|
+
};
|
|
49607
|
+
const list = (sessionId) => {
|
|
49608
|
+
const sessionWatchers = bySession.get(sessionId);
|
|
49609
|
+
if (!sessionWatchers)
|
|
49610
|
+
return [];
|
|
49611
|
+
pruneStaleFromSession(sessionId, sessionWatchers);
|
|
49612
|
+
return Array.from(sessionWatchers.values());
|
|
49613
|
+
};
|
|
49614
|
+
return {
|
|
49615
|
+
heartbeat,
|
|
49616
|
+
join: join5,
|
|
49617
|
+
leave,
|
|
49618
|
+
list,
|
|
49619
|
+
setRole,
|
|
49620
|
+
sessionsWatchedBy(supervisorId) {
|
|
49621
|
+
const out = [];
|
|
49622
|
+
for (const [sessionId, map] of bySession) {
|
|
49623
|
+
if (map.has(supervisorId))
|
|
49624
|
+
out.push(sessionId);
|
|
49625
|
+
}
|
|
49626
|
+
return out;
|
|
49627
|
+
},
|
|
49628
|
+
subscribe(listener) {
|
|
49629
|
+
listeners.add(listener);
|
|
49630
|
+
return () => {
|
|
49631
|
+
listeners.delete(listener);
|
|
49632
|
+
};
|
|
49633
|
+
}
|
|
49634
|
+
};
|
|
49635
|
+
};
|
|
49636
|
+
// src/supervisorPermissions.ts
|
|
49637
|
+
var TIER_CAPABILITIES = {
|
|
49638
|
+
annotate: ["monitor", "annotate"],
|
|
49639
|
+
coach: ["monitor", "annotate", "coach"],
|
|
49640
|
+
"full-control": [
|
|
49641
|
+
"monitor",
|
|
49642
|
+
"annotate",
|
|
49643
|
+
"coach",
|
|
49644
|
+
"whisper",
|
|
49645
|
+
"barge",
|
|
49646
|
+
"takeover",
|
|
49647
|
+
"release",
|
|
49648
|
+
"end-call",
|
|
49649
|
+
"view-pii",
|
|
49650
|
+
"export-recording"
|
|
49651
|
+
],
|
|
49652
|
+
"monitor-only": ["monitor"],
|
|
49653
|
+
whisper: ["monitor", "annotate", "coach", "whisper"]
|
|
49654
|
+
};
|
|
49655
|
+
var createVoiceSupervisorPermissions = (options = {}) => {
|
|
49656
|
+
const now = options.now ?? (() => Date.now());
|
|
49657
|
+
const store = new Map;
|
|
49658
|
+
for (const permission of options.permissions ?? []) {
|
|
49659
|
+
store.set(permission.supervisorId, permission);
|
|
49660
|
+
}
|
|
49661
|
+
const defaultTier = options.defaultTier ?? null;
|
|
49662
|
+
const get = (supervisorId) => {
|
|
49663
|
+
const permission = store.get(supervisorId);
|
|
49664
|
+
if (!permission) {
|
|
49665
|
+
return defaultTier ? { supervisorId, tier: defaultTier } : null;
|
|
49666
|
+
}
|
|
49667
|
+
if (permission.expiresAt !== undefined && permission.expiresAt <= now()) {
|
|
49668
|
+
return null;
|
|
49669
|
+
}
|
|
49670
|
+
return permission;
|
|
49671
|
+
};
|
|
49672
|
+
const capabilitiesFor = (supervisorId) => {
|
|
49673
|
+
const permission = get(supervisorId);
|
|
49674
|
+
if (!permission)
|
|
49675
|
+
return [];
|
|
49676
|
+
const base = new Set(TIER_CAPABILITIES[permission.tier]);
|
|
49677
|
+
for (const extra of permission.extraCapabilities ?? [])
|
|
49678
|
+
base.add(extra);
|
|
49679
|
+
for (const denied of permission.deniedCapabilities ?? [])
|
|
49680
|
+
base.delete(denied);
|
|
49681
|
+
return Array.from(base);
|
|
49682
|
+
};
|
|
49683
|
+
const can = (supervisorId, capability) => {
|
|
49684
|
+
const permission = store.get(supervisorId);
|
|
49685
|
+
if (!permission) {
|
|
49686
|
+
if (!defaultTier)
|
|
49687
|
+
return { allowed: false, reason: "no-permission" };
|
|
49688
|
+
} else if (permission.expiresAt !== undefined && permission.expiresAt <= now()) {
|
|
49689
|
+
return { allowed: false, reason: "expired" };
|
|
49690
|
+
} else if (permission.deniedCapabilities?.includes(capability)) {
|
|
49691
|
+
return { allowed: false, reason: "denied" };
|
|
49692
|
+
}
|
|
49693
|
+
if (capabilitiesFor(supervisorId).includes(capability)) {
|
|
49694
|
+
return { allowed: true };
|
|
49695
|
+
}
|
|
49696
|
+
return { allowed: false, reason: "tier-too-low" };
|
|
49697
|
+
};
|
|
49698
|
+
const grant = (supervisorId, tier, options2 = {}) => {
|
|
49699
|
+
const permission = {
|
|
49700
|
+
supervisorId,
|
|
49701
|
+
tier,
|
|
49702
|
+
...options2.extraCapabilities !== undefined ? { extraCapabilities: options2.extraCapabilities } : {},
|
|
49703
|
+
...options2.deniedCapabilities !== undefined ? { deniedCapabilities: options2.deniedCapabilities } : {},
|
|
49704
|
+
...options2.expiresAt !== undefined ? { expiresAt: options2.expiresAt } : {}
|
|
49705
|
+
};
|
|
49706
|
+
store.set(supervisorId, permission);
|
|
49707
|
+
return permission;
|
|
49708
|
+
};
|
|
49709
|
+
const revoke = (supervisorId) => store.delete(supervisorId);
|
|
49710
|
+
const enforce = (supervisorId, capability) => {
|
|
49711
|
+
const verdict = can(supervisorId, capability);
|
|
49712
|
+
if (!verdict.allowed) {
|
|
49713
|
+
throw new Error(`Supervisor ${supervisorId} cannot ${capability}: ${verdict.reason ?? "denied"}`);
|
|
49714
|
+
}
|
|
49715
|
+
};
|
|
49716
|
+
return {
|
|
49717
|
+
can,
|
|
49718
|
+
capabilitiesFor,
|
|
49719
|
+
enforce,
|
|
49720
|
+
get,
|
|
49721
|
+
grant,
|
|
49722
|
+
revoke,
|
|
49723
|
+
tiers: () => Object.keys(TIER_CAPABILITIES)
|
|
49724
|
+
};
|
|
49725
|
+
};
|
|
49726
|
+
var VOICE_SUPERVISOR_TIER_CAPABILITIES = TIER_CAPABILITIES;
|
|
49727
|
+
// src/calendarAdapter.ts
|
|
49728
|
+
var createVoiceInMemoryCalendarAdapter = (options) => {
|
|
49729
|
+
const now = options.now ?? (() => Date.now());
|
|
49730
|
+
const generateId = options.generateId ?? (() => `appt_${Math.random().toString(36).slice(2, 10)}`);
|
|
49731
|
+
const appointments = new Map;
|
|
49732
|
+
for (const range of options.bookedRanges ?? []) {
|
|
49733
|
+
const id = generateId();
|
|
49734
|
+
appointments.set(id, {
|
|
49735
|
+
calendarId: "default",
|
|
49736
|
+
createdAt: now(),
|
|
49737
|
+
endMs: range.endMs,
|
|
49738
|
+
id,
|
|
49739
|
+
startMs: range.startMs,
|
|
49740
|
+
status: "scheduled"
|
|
49741
|
+
});
|
|
49742
|
+
}
|
|
49743
|
+
const liveRanges = () => Array.from(appointments.values()).filter((a) => a.status === "scheduled").map((a) => ({ endMs: a.endMs, startMs: a.startMs }));
|
|
49744
|
+
return {
|
|
49745
|
+
async book(input) {
|
|
49746
|
+
const clash = liveRanges().some((r) => input.startMs < r.endMs && r.startMs < input.endMs);
|
|
49747
|
+
if (clash)
|
|
49748
|
+
throw new Error("Slot is already booked");
|
|
49749
|
+
const id = generateId();
|
|
49750
|
+
const appointment = {
|
|
49751
|
+
calendarId: input.calendarId,
|
|
49752
|
+
createdAt: now(),
|
|
49753
|
+
endMs: input.endMs,
|
|
49754
|
+
id,
|
|
49755
|
+
startMs: input.startMs,
|
|
49756
|
+
status: "scheduled",
|
|
49757
|
+
...input.title !== undefined ? { title: input.title } : {},
|
|
49758
|
+
...input.attendees !== undefined ? { attendees: input.attendees } : {},
|
|
49759
|
+
...input.notes !== undefined ? { notes: input.notes } : {},
|
|
49760
|
+
...input.metadata !== undefined ? { metadata: input.metadata } : {}
|
|
49761
|
+
};
|
|
49762
|
+
appointments.set(id, appointment);
|
|
49763
|
+
return appointment;
|
|
49764
|
+
},
|
|
49765
|
+
async cancel(id) {
|
|
49766
|
+
const existing = appointments.get(id);
|
|
49767
|
+
if (!existing)
|
|
49768
|
+
return null;
|
|
49769
|
+
const cancelled = {
|
|
49770
|
+
...existing,
|
|
49771
|
+
status: "cancelled"
|
|
49772
|
+
};
|
|
49773
|
+
appointments.set(id, cancelled);
|
|
49774
|
+
return cancelled;
|
|
49775
|
+
},
|
|
49776
|
+
async get(id) {
|
|
49777
|
+
return appointments.get(id) ?? null;
|
|
49778
|
+
},
|
|
49779
|
+
async listAvailability(query) {
|
|
49780
|
+
const { generateVoiceCalendarSlots: generateVoiceCalendarSlots2 } = await Promise.resolve().then(() => exports_calendarSlots);
|
|
49781
|
+
return generateVoiceCalendarSlots2({
|
|
49782
|
+
bookedRanges: liveRanges(),
|
|
49783
|
+
...query.bufferMinutes !== undefined ? { bufferMinutes: query.bufferMinutes } : {},
|
|
49784
|
+
businessHours: options.businessHours,
|
|
49785
|
+
durationMinutes: query.durationMinutes,
|
|
49786
|
+
fromMs: query.fromMs,
|
|
49787
|
+
...query.granularityMinutes !== undefined ? { granularityMinutes: query.granularityMinutes } : {},
|
|
49788
|
+
...query.maxSlots !== undefined ? { maxSlots: query.maxSlots } : {},
|
|
49789
|
+
...options.timezone !== undefined ? { timezone: options.timezone } : {},
|
|
49790
|
+
toMs: query.toMs
|
|
49791
|
+
});
|
|
49792
|
+
},
|
|
49793
|
+
providerName: "in-memory",
|
|
49794
|
+
async reschedule(id, nextStartMs, nextEndMs) {
|
|
49795
|
+
const existing = appointments.get(id);
|
|
49796
|
+
if (!existing)
|
|
49797
|
+
return null;
|
|
49798
|
+
const others = liveRanges().filter((r) => r.startMs !== existing.startMs || r.endMs !== existing.endMs);
|
|
49799
|
+
const clash = others.some((r) => nextStartMs < r.endMs && r.startMs < nextEndMs);
|
|
49800
|
+
if (clash)
|
|
49801
|
+
throw new Error("Cannot reschedule onto a booked slot");
|
|
49802
|
+
const updated = {
|
|
49803
|
+
...existing,
|
|
49804
|
+
endMs: nextEndMs,
|
|
49805
|
+
startMs: nextStartMs
|
|
49806
|
+
};
|
|
49807
|
+
appointments.set(id, updated);
|
|
49808
|
+
return updated;
|
|
49809
|
+
}
|
|
49810
|
+
};
|
|
49811
|
+
};
|
|
49812
|
+
// src/bookingFlow.ts
|
|
49813
|
+
var createVoiceBookingFlow = (options) => {
|
|
49814
|
+
const initial = {
|
|
49815
|
+
proposedSlots: [],
|
|
49816
|
+
step: options.initialStep ?? (options.services ? "ask-service" : "ask-date")
|
|
49817
|
+
};
|
|
49818
|
+
let state = initial;
|
|
49819
|
+
const listeners = new Set;
|
|
49820
|
+
const setState = (next) => {
|
|
49821
|
+
state = { ...state, ...next };
|
|
49822
|
+
for (const listener of listeners)
|
|
49823
|
+
listener(state);
|
|
49824
|
+
};
|
|
49825
|
+
const chooseService = (serviceId) => {
|
|
49826
|
+
const services = options.services ?? [];
|
|
49827
|
+
const service = services.find((s) => s.id === serviceId);
|
|
49828
|
+
if (!service) {
|
|
49829
|
+
setState({ error: `Unknown service: ${serviceId}`, step: "failed" });
|
|
49830
|
+
return;
|
|
49831
|
+
}
|
|
49832
|
+
setState({
|
|
49833
|
+
serviceDurationMinutes: service.durationMinutes,
|
|
49834
|
+
serviceId: service.id,
|
|
49835
|
+
step: "ask-date"
|
|
49836
|
+
});
|
|
49837
|
+
};
|
|
49838
|
+
const proposeSlotsForDay = async (input) => {
|
|
49839
|
+
const duration = state.serviceDurationMinutes ?? options.defaultDurationMinutes ?? 30;
|
|
49840
|
+
const slots = await options.adapter.listAvailability({
|
|
49841
|
+
calendarId: options.calendarId,
|
|
49842
|
+
durationMinutes: duration,
|
|
49843
|
+
fromMs: input.fromMs,
|
|
49844
|
+
...options.maxSlotsPerDay !== undefined ? { maxSlots: options.maxSlotsPerDay } : {},
|
|
49845
|
+
toMs: input.toMs
|
|
49846
|
+
});
|
|
49847
|
+
setState({ proposedSlots: slots, step: slots.length > 0 ? "ask-time" : "ask-date" });
|
|
49848
|
+
return slots;
|
|
49849
|
+
};
|
|
49850
|
+
const chooseSlot = (slotIndex) => {
|
|
49851
|
+
const slot = state.proposedSlots[slotIndex];
|
|
49852
|
+
if (!slot) {
|
|
49853
|
+
setState({ error: "Invalid slot selection", step: "failed" });
|
|
49854
|
+
return;
|
|
49855
|
+
}
|
|
49856
|
+
setState({ selectedSlot: slot, step: "confirm" });
|
|
49857
|
+
};
|
|
49858
|
+
const confirm = async (input = {}) => {
|
|
49859
|
+
if (state.step !== "confirm" || !state.selectedSlot) {
|
|
49860
|
+
setState({ error: "Nothing to confirm", step: "failed" });
|
|
49861
|
+
return null;
|
|
49862
|
+
}
|
|
49863
|
+
setState({ step: "booking" });
|
|
49864
|
+
try {
|
|
49865
|
+
const appt = await options.adapter.book({
|
|
49866
|
+
calendarId: options.calendarId,
|
|
49867
|
+
endMs: state.selectedSlot.endMs,
|
|
49868
|
+
startMs: state.selectedSlot.startMs,
|
|
49869
|
+
...input.attendees !== undefined ? { attendees: input.attendees } : {},
|
|
49870
|
+
...input.title !== undefined ? { title: input.title } : {},
|
|
49871
|
+
...input.notes !== undefined ? { notes: input.notes } : {}
|
|
49872
|
+
});
|
|
49873
|
+
setState({ appointment: appt, step: "booked" });
|
|
49874
|
+
return appt;
|
|
49875
|
+
} catch (error) {
|
|
49876
|
+
setState({
|
|
49877
|
+
error: error instanceof Error ? error.message : String(error),
|
|
49878
|
+
step: "failed"
|
|
49879
|
+
});
|
|
49880
|
+
return null;
|
|
49881
|
+
}
|
|
49882
|
+
};
|
|
49883
|
+
const reset = () => {
|
|
49884
|
+
state = {
|
|
49885
|
+
proposedSlots: [],
|
|
49886
|
+
step: options.initialStep ?? (options.services ? "ask-service" : "ask-date")
|
|
49887
|
+
};
|
|
49888
|
+
for (const listener of listeners)
|
|
49889
|
+
listener(state);
|
|
49890
|
+
};
|
|
49891
|
+
return {
|
|
49892
|
+
chooseService,
|
|
49893
|
+
chooseSlot,
|
|
49894
|
+
confirm,
|
|
49895
|
+
getState: () => state,
|
|
49896
|
+
proposeSlotsForDay,
|
|
49897
|
+
reset,
|
|
49898
|
+
subscribe(listener) {
|
|
49899
|
+
listeners.add(listener);
|
|
49900
|
+
listener(state);
|
|
49901
|
+
return () => {
|
|
49902
|
+
listeners.delete(listener);
|
|
49903
|
+
};
|
|
49904
|
+
}
|
|
49905
|
+
};
|
|
49906
|
+
};
|
|
49907
|
+
// src/noShowPredictor.ts
|
|
49908
|
+
var clamp = (value, min = 0, max = 1) => Math.max(min, Math.min(max, value));
|
|
49909
|
+
var scoreVoiceNoShowRisk = (input) => {
|
|
49910
|
+
const now = input.now ?? (() => Date.now());
|
|
49911
|
+
const leadHours = (input.appointmentStartMs - input.bookedAtMs) / 3600000;
|
|
49912
|
+
const startDate = new Date(input.appointmentStartMs);
|
|
49913
|
+
const weekday = startDate.getUTCDay();
|
|
49914
|
+
const hour = startDate.getUTCHours();
|
|
49915
|
+
const history = input.history ?? [];
|
|
49916
|
+
const past = history.filter((r) => r.scheduledStartMs < input.appointmentStartMs);
|
|
49917
|
+
const priorNoShows = past.filter((r) => r.outcome === "no-show").length;
|
|
49918
|
+
const priorKept = past.filter((r) => r.outcome === "kept").length;
|
|
49919
|
+
let score = 0.15;
|
|
49920
|
+
const drivers = [];
|
|
49921
|
+
if (leadHours > 72) {
|
|
49922
|
+
score += 0.1;
|
|
49923
|
+
drivers.push({ kind: "lead-time-hours", value: leadHours });
|
|
49924
|
+
} else if (leadHours < 12) {
|
|
49925
|
+
score -= 0.05;
|
|
49926
|
+
drivers.push({ kind: "lead-time-hours", value: leadHours });
|
|
49927
|
+
}
|
|
49928
|
+
if (weekday === 1) {
|
|
49929
|
+
score += 0.04;
|
|
49930
|
+
drivers.push({ kind: "weekday", value: weekday });
|
|
49931
|
+
}
|
|
49932
|
+
if (weekday === 5) {
|
|
49933
|
+
score += 0.03;
|
|
49934
|
+
drivers.push({ kind: "weekday", value: weekday });
|
|
49935
|
+
}
|
|
49936
|
+
if (hour < 9 || hour >= 17) {
|
|
49937
|
+
score += 0.04;
|
|
49938
|
+
drivers.push({ kind: "hour-of-day", value: hour });
|
|
49939
|
+
}
|
|
49940
|
+
if (priorNoShows > 0) {
|
|
49941
|
+
const delta = Math.min(0.5, priorNoShows * 0.2);
|
|
49942
|
+
score += delta;
|
|
49943
|
+
drivers.push({ kind: "prior-no-show-count", value: priorNoShows });
|
|
49944
|
+
}
|
|
49945
|
+
if (priorKept > 2 && priorNoShows === 0) {
|
|
49946
|
+
score -= 0.08;
|
|
49947
|
+
drivers.push({ kind: "prior-kept-count", value: priorKept });
|
|
49948
|
+
}
|
|
49949
|
+
if (input.reminderConfirmed === true) {
|
|
49950
|
+
score -= 0.15;
|
|
49951
|
+
drivers.push({ kind: "reminder-confirmed", value: true });
|
|
49952
|
+
} else if (input.reminderConfirmed === false) {
|
|
49953
|
+
score += 0.1;
|
|
49954
|
+
drivers.push({ kind: "reminder-confirmed", value: false });
|
|
49955
|
+
}
|
|
49956
|
+
if (input.callbackDistanceHours !== undefined && input.callbackDistanceHours > 24) {
|
|
49957
|
+
score += 0.06;
|
|
49958
|
+
drivers.push({
|
|
49959
|
+
kind: "callback-distance-hours",
|
|
49960
|
+
value: input.callbackDistanceHours
|
|
49961
|
+
});
|
|
49962
|
+
}
|
|
49963
|
+
if (input.weatherDisruption) {
|
|
49964
|
+
score += 0.18;
|
|
49965
|
+
drivers.push({ kind: "weather-disruption", value: true });
|
|
49966
|
+
}
|
|
49967
|
+
const finalScore = clamp(score);
|
|
49968
|
+
const band = finalScore >= 0.55 ? "high" : finalScore >= 0.3 ? "moderate" : "low";
|
|
49969
|
+
return { band, drivers, score: finalScore };
|
|
49970
|
+
};
|
|
49971
|
+
var summarizeVoiceNoShowVerdict = (verdict) => {
|
|
49972
|
+
const pct = Math.round(verdict.score * 100);
|
|
49973
|
+
const top = verdict.drivers.slice(0, 2).map((d) => d.kind).join(", ");
|
|
49974
|
+
return `${verdict.band} risk (${pct}%)${top ? ` \u2014 driven by ${top}` : ""}`;
|
|
49975
|
+
};
|
|
49976
|
+
// src/reminderScheduler.ts
|
|
49977
|
+
var DEFAULT_VOICE_REMINDER_TRIGGERS = [
|
|
49978
|
+
{
|
|
49979
|
+
channel: "sms",
|
|
49980
|
+
id: "remind-24h",
|
|
49981
|
+
offsetMinutesBeforeStart: 24 * 60
|
|
49982
|
+
},
|
|
49983
|
+
{
|
|
49984
|
+
channel: "sms",
|
|
49985
|
+
id: "remind-2h",
|
|
49986
|
+
offsetMinutesBeforeStart: 120
|
|
49987
|
+
},
|
|
49988
|
+
{
|
|
49989
|
+
channel: "call",
|
|
49990
|
+
id: "remind-call-30m",
|
|
49991
|
+
offsetMinutesBeforeStart: 30,
|
|
49992
|
+
retryOnFailure: true
|
|
49993
|
+
}
|
|
49994
|
+
];
|
|
49995
|
+
var createVoiceReminderScheduler = (options = {}) => {
|
|
49996
|
+
const now = options.now ?? (() => Date.now());
|
|
49997
|
+
const generateId = options.generateJobId ?? (() => `rem_${Math.random().toString(36).slice(2, 10)}`);
|
|
49998
|
+
const defaultTriggers = options.defaultTriggers ?? DEFAULT_VOICE_REMINDER_TRIGGERS;
|
|
49999
|
+
const maxAttempts = options.maxAttempts ?? 2;
|
|
50000
|
+
const jobs = new Map;
|
|
50001
|
+
const listeners = new Set;
|
|
50002
|
+
const broadcast = (job) => {
|
|
50003
|
+
for (const listener of listeners)
|
|
50004
|
+
listener(job);
|
|
50005
|
+
};
|
|
50006
|
+
const schedule = (input) => {
|
|
50007
|
+
const triggers = input.triggers.length > 0 ? input.triggers : defaultTriggers;
|
|
50008
|
+
const at = now();
|
|
50009
|
+
const created = [];
|
|
50010
|
+
for (const trigger of triggers) {
|
|
50011
|
+
const fireAt = input.appointmentStartMs - trigger.offsetMinutesBeforeStart * 60000;
|
|
50012
|
+
if (fireAt <= at)
|
|
50013
|
+
continue;
|
|
50014
|
+
const job = {
|
|
50015
|
+
appointmentId: input.appointmentId,
|
|
50016
|
+
attempts: 0,
|
|
50017
|
+
channel: trigger.channel,
|
|
50018
|
+
id: generateId(),
|
|
50019
|
+
scheduledAtMs: fireAt,
|
|
50020
|
+
status: "pending",
|
|
50021
|
+
triggerId: trigger.id,
|
|
50022
|
+
...input.metadata !== undefined ? { metadata: input.metadata } : {}
|
|
50023
|
+
};
|
|
50024
|
+
jobs.set(job.id, job);
|
|
50025
|
+
created.push(job);
|
|
50026
|
+
broadcast(job);
|
|
50027
|
+
}
|
|
50028
|
+
return created;
|
|
50029
|
+
};
|
|
50030
|
+
const due = (at = now()) => Array.from(jobs.values()).filter((j) => j.status === "pending" && j.scheduledAtMs <= at);
|
|
50031
|
+
const markInFlight = (jobId) => {
|
|
50032
|
+
const job = jobs.get(jobId);
|
|
50033
|
+
if (!job || job.status !== "pending")
|
|
50034
|
+
return false;
|
|
50035
|
+
job.status = "in-flight";
|
|
50036
|
+
job.attempts += 1;
|
|
50037
|
+
broadcast(job);
|
|
50038
|
+
return true;
|
|
50039
|
+
};
|
|
50040
|
+
const markSent = (jobId) => {
|
|
50041
|
+
const job = jobs.get(jobId);
|
|
50042
|
+
if (!job)
|
|
50043
|
+
return false;
|
|
50044
|
+
job.status = "sent";
|
|
50045
|
+
broadcast(job);
|
|
50046
|
+
return true;
|
|
50047
|
+
};
|
|
50048
|
+
const markFailed = (jobId, error) => {
|
|
50049
|
+
const job = jobs.get(jobId);
|
|
50050
|
+
if (!job)
|
|
50051
|
+
return false;
|
|
50052
|
+
job.lastError = error;
|
|
50053
|
+
if (job.attempts < maxAttempts) {
|
|
50054
|
+
job.status = "pending";
|
|
50055
|
+
job.scheduledAtMs = now() + 5 * 60000;
|
|
50056
|
+
} else {
|
|
50057
|
+
job.status = "failed";
|
|
50058
|
+
}
|
|
50059
|
+
broadcast(job);
|
|
50060
|
+
return true;
|
|
50061
|
+
};
|
|
50062
|
+
const cancelForAppointment = (appointmentId) => {
|
|
50063
|
+
let count = 0;
|
|
50064
|
+
for (const job of jobs.values()) {
|
|
50065
|
+
if (job.appointmentId === appointmentId && (job.status === "pending" || job.status === "in-flight")) {
|
|
50066
|
+
job.status = "cancelled";
|
|
50067
|
+
broadcast(job);
|
|
50068
|
+
count += 1;
|
|
50069
|
+
}
|
|
50070
|
+
}
|
|
50071
|
+
return count;
|
|
50072
|
+
};
|
|
50073
|
+
return {
|
|
50074
|
+
cancelForAppointment,
|
|
50075
|
+
due,
|
|
50076
|
+
list: (appointmentId) => Array.from(jobs.values()).filter((j) => !appointmentId || j.appointmentId === appointmentId),
|
|
50077
|
+
markFailed,
|
|
50078
|
+
markInFlight,
|
|
50079
|
+
markSent,
|
|
50080
|
+
schedule,
|
|
50081
|
+
subscribe(listener) {
|
|
50082
|
+
listeners.add(listener);
|
|
50083
|
+
return () => {
|
|
50084
|
+
listeners.delete(listener);
|
|
50085
|
+
};
|
|
50086
|
+
}
|
|
50087
|
+
};
|
|
50088
|
+
};
|
|
49174
50089
|
export {
|
|
49175
50090
|
writeVoiceProofPack,
|
|
49176
50091
|
writeVoiceMediaPipelineArtifacts,
|
|
@@ -49218,6 +50133,7 @@ export {
|
|
|
49218
50133
|
summarizeVoiceOpsTaskQueue,
|
|
49219
50134
|
summarizeVoiceOpsTaskAnalytics,
|
|
49220
50135
|
summarizeVoiceOpsStatus,
|
|
50136
|
+
summarizeVoiceNoShowVerdict,
|
|
49221
50137
|
summarizeVoiceMediaPipelineReport,
|
|
49222
50138
|
summarizeVoiceLiveLatency,
|
|
49223
50139
|
summarizeVoiceIntegrationEvents,
|
|
@@ -49226,6 +50142,7 @@ export {
|
|
|
49226
50142
|
summarizeVoiceCampaigns,
|
|
49227
50143
|
summarizeVoiceCampaignDispositions,
|
|
49228
50144
|
summarizeVoiceCallerTranscript,
|
|
50145
|
+
summarizeVoiceCalendarSlot,
|
|
49229
50146
|
summarizeVoiceBrowserMedia,
|
|
49230
50147
|
summarizeVoiceBargeIn,
|
|
49231
50148
|
summarizeVoiceAuditTrail,
|
|
@@ -49239,6 +50156,7 @@ export {
|
|
|
49239
50156
|
shouldRetryCampaignAttempt,
|
|
49240
50157
|
shapeTelephonyAssistantText,
|
|
49241
50158
|
selectVoiceTraceEventsForPrune,
|
|
50159
|
+
scoreVoiceNoShowRisk,
|
|
49242
50160
|
saveVoiceIncidentBundleArtifact,
|
|
49243
50161
|
runVoiceToolContractSuite,
|
|
49244
50162
|
runVoiceToolContract,
|
|
@@ -49446,6 +50364,7 @@ export {
|
|
|
49446
50364
|
getLatestVoiceTelephonyMediaReport,
|
|
49447
50365
|
getLatestVoiceBrowserMediaReport,
|
|
49448
50366
|
getDefaultVoiceTelephonyBenchmarkScenarios,
|
|
50367
|
+
generateVoiceCalendarSlots,
|
|
49449
50368
|
fromVapiAssistantConfig,
|
|
49450
50369
|
formatVoiceProofTrendAge,
|
|
49451
50370
|
formatVoiceCallPlayerTimestamp,
|
|
@@ -49521,6 +50440,7 @@ export {
|
|
|
49521
50440
|
createVoiceWorkflowContractPreset,
|
|
49522
50441
|
createVoiceWorkflowContractHandler,
|
|
49523
50442
|
createVoiceWorkflowContract,
|
|
50443
|
+
createVoiceWhisperChannel,
|
|
49524
50444
|
createVoiceWebhookHandoffAdapter,
|
|
49525
50445
|
createVoiceWebhookFanout,
|
|
49526
50446
|
createVoiceWebhookDeliveryWorkerLoop,
|
|
@@ -49538,6 +50458,7 @@ export {
|
|
|
49538
50458
|
createVoiceTurnLatencyHTMLHandler,
|
|
49539
50459
|
createVoiceTransferCallTool,
|
|
49540
50460
|
createVoiceTranscriptRedactor,
|
|
50461
|
+
createVoiceTranscriptAnnotator,
|
|
49541
50462
|
createVoiceTraceTimelineRoutes,
|
|
49542
50463
|
createVoiceTraceSinkStore,
|
|
49543
50464
|
createVoiceTraceSinkDeliveryWorkerLoop,
|
|
@@ -49572,6 +50493,8 @@ export {
|
|
|
49572
50493
|
createVoiceTaskSLABreachedEvent,
|
|
49573
50494
|
createVoiceTaskCreatedEvent,
|
|
49574
50495
|
createVoiceTTSProviderRouter,
|
|
50496
|
+
createVoiceSupervisorPresence,
|
|
50497
|
+
createVoiceSupervisorPermissions,
|
|
49575
50498
|
createVoiceSloThresholdProfile,
|
|
49576
50499
|
createVoiceSloReadinessThresholdRoutes,
|
|
49577
50500
|
createVoiceSloReadinessThresholdOptions,
|
|
@@ -49618,6 +50541,7 @@ export {
|
|
|
49618
50541
|
createVoiceRetentionScheduler,
|
|
49619
50542
|
createVoiceResilienceRoutes,
|
|
49620
50543
|
createVoiceReplayTimelineHTMXRoute,
|
|
50544
|
+
createVoiceReminderScheduler,
|
|
49621
50545
|
createVoiceRedisTelnyxWebhookEventStore,
|
|
49622
50546
|
createVoiceRedisTelephonyWebhookIdempotencyStore,
|
|
49623
50547
|
createVoiceRedisTaskLeaseCoordinator,
|
|
@@ -49744,6 +50668,7 @@ export {
|
|
|
49744
50668
|
createVoiceLiveOpsController,
|
|
49745
50669
|
createVoiceLiveMonitorRoutes,
|
|
49746
50670
|
createVoiceLiveLatencyRoutes,
|
|
50671
|
+
createVoiceLiveCoach,
|
|
49747
50672
|
createVoiceLiveCallViewerHTMXRoute,
|
|
49748
50673
|
createVoiceLinearIssueUpdateSink,
|
|
49749
50674
|
createVoiceLinearIssueSyncSinks,
|
|
@@ -49757,6 +50682,7 @@ export {
|
|
|
49757
50682
|
createVoiceIncidentBundleRoutes,
|
|
49758
50683
|
createVoiceInMemoryRealCallProfileRecoveryJobStore,
|
|
49759
50684
|
createVoiceInMemoryMonitorRegistry,
|
|
50685
|
+
createVoiceInMemoryCalendarAdapter,
|
|
49760
50686
|
createVoiceIVRSession,
|
|
49761
50687
|
createVoiceHubSpotTaskUpdateSink,
|
|
49762
50688
|
createVoiceHubSpotTaskSyncSinks,
|
|
@@ -49831,6 +50757,7 @@ export {
|
|
|
49831
50757
|
createVoiceCRMActivitySink,
|
|
49832
50758
|
createVoiceBrowserMediaRoutes,
|
|
49833
50759
|
createVoiceBrowserCallProfileRoutes,
|
|
50760
|
+
createVoiceBookingFlow,
|
|
49834
50761
|
createVoiceBearerAuthVerifier,
|
|
49835
50762
|
createVoiceBargeInRoutes,
|
|
49836
50763
|
createVoiceBackchannelDriver,
|
|
@@ -50053,10 +50980,12 @@ export {
|
|
|
50053
50980
|
VOICE_WEBHOOK_TIMESTAMP_HEADER,
|
|
50054
50981
|
VOICE_WEBHOOK_SIGNATURE_HEADER,
|
|
50055
50982
|
VOICE_TCPA_DEFAULT_WINDOW,
|
|
50983
|
+
VOICE_SUPERVISOR_TIER_CAPABILITIES,
|
|
50056
50984
|
VOICE_LIVE_OPS_ACTIONS,
|
|
50057
50985
|
VOICE_DTMF_DIGITS,
|
|
50058
50986
|
VOICE_CALLER_MEMORY_KEY,
|
|
50059
50987
|
TURN_PROFILE_DEFAULTS,
|
|
50988
|
+
DEFAULT_VOICE_REMINDER_TRIGGERS,
|
|
50060
50989
|
DEFAULT_VOICE_REDACTION_PATTERNS,
|
|
50061
50990
|
DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS,
|
|
50062
50991
|
DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS,
|
|
@@ -50065,5 +50994,6 @@ export {
|
|
|
50065
50994
|
DEFAULT_VOICE_POST_CALL_SURVEY_QUESTIONS,
|
|
50066
50995
|
DEFAULT_VOICE_CAMPAIGN_TEMPLATE_FILTERS,
|
|
50067
50996
|
DEFAULT_VOICE_CALL_DISPOSITIONS,
|
|
50997
|
+
DEFAULT_VOICE_ANNOTATION_KIND_SEVERITY,
|
|
50068
50998
|
BROWSER_NOISE_SUPPRESSOR_PRESETS
|
|
50069
50999
|
};
|