@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/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
  };