@absolutejs/voice 0.0.22-beta.356 → 0.0.22-beta.358

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
@@ -3703,6 +3703,7 @@ var createVoiceSession = (options) => {
3703
3703
  partial: session.currentTurn.partialText,
3704
3704
  scenarioId: session.scenarioId,
3705
3705
  sessionId: options.id,
3706
+ sessionMetadata: session.metadata && typeof session.metadata === "object" ? session.metadata : undefined,
3706
3707
  status: session.status,
3707
3708
  turns: session.turns,
3708
3709
  type: "replay"
@@ -4994,6 +4995,12 @@ var createVoiceSession = (options) => {
4994
4995
  if (options.scenarioId && session.scenarioId !== options.scenarioId) {
4995
4996
  session.scenarioId = options.scenarioId;
4996
4997
  }
4998
+ if (options.sessionMetadata) {
4999
+ session.metadata = {
5000
+ ...session.metadata && typeof session.metadata === "object" ? session.metadata : {},
5001
+ ...options.sessionMetadata
5002
+ };
5003
+ }
4997
5004
  ensureCommittedTurnGuard(session);
4998
5005
  let shouldFireOnSession = !existingSession;
4999
5006
  if (existingSession?.scenarioId && options.scenarioId && existingSession.scenarioId !== options.scenarioId) {
@@ -5042,6 +5049,7 @@ var createVoiceSession = (options) => {
5042
5049
  await send({
5043
5050
  sessionId: options.id,
5044
5051
  status: session.status,
5052
+ sessionMetadata: session.metadata && typeof session.metadata === "object" ? session.metadata : undefined,
5045
5053
  scenarioId: session.scenarioId,
5046
5054
  type: "session"
5047
5055
  });
@@ -5200,6 +5208,319 @@ var createVoiceSession = (options) => {
5200
5208
  return api;
5201
5209
  };
5202
5210
 
5211
+ // src/audit.ts
5212
+ var includes = (filter, value) => {
5213
+ if (!filter) {
5214
+ return true;
5215
+ }
5216
+ if (!value) {
5217
+ return false;
5218
+ }
5219
+ return Array.isArray(filter) ? filter.includes(value) : filter === value;
5220
+ };
5221
+ var createVoiceAuditEvent = (event) => ({
5222
+ ...event,
5223
+ at: event.at ?? Date.now(),
5224
+ id: event.id ?? crypto.randomUUID()
5225
+ });
5226
+ var filterVoiceAuditEvents = (events, filter = {}) => {
5227
+ const sorted = events.filter((event) => {
5228
+ if (!includes(filter.type, event.type)) {
5229
+ return false;
5230
+ }
5231
+ if (!includes(filter.outcome, event.outcome)) {
5232
+ return false;
5233
+ }
5234
+ if (filter.actorId && event.actor?.id !== filter.actorId) {
5235
+ return false;
5236
+ }
5237
+ if (filter.resourceId && event.resource?.id !== filter.resourceId) {
5238
+ return false;
5239
+ }
5240
+ if (filter.resourceType && event.resource?.type !== filter.resourceType) {
5241
+ return false;
5242
+ }
5243
+ if (filter.sessionId && event.sessionId !== filter.sessionId) {
5244
+ return false;
5245
+ }
5246
+ if (filter.traceId && event.traceId !== filter.traceId) {
5247
+ return false;
5248
+ }
5249
+ if (typeof filter.after === "number" && event.at <= filter.after) {
5250
+ return false;
5251
+ }
5252
+ if (typeof filter.afterOrAt === "number" && event.at < filter.afterOrAt) {
5253
+ return false;
5254
+ }
5255
+ if (typeof filter.before === "number" && event.at >= filter.before) {
5256
+ return false;
5257
+ }
5258
+ if (typeof filter.beforeOrAt === "number" && event.at > filter.beforeOrAt) {
5259
+ return false;
5260
+ }
5261
+ return true;
5262
+ }).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
5263
+ return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
5264
+ };
5265
+ var createVoiceMemoryAuditEventStore = () => {
5266
+ const events = new Map;
5267
+ return {
5268
+ append: (event) => {
5269
+ const stored = createVoiceAuditEvent(event);
5270
+ events.set(stored.id, stored);
5271
+ return stored;
5272
+ },
5273
+ get: (id) => events.get(id),
5274
+ list: (filter) => filterVoiceAuditEvents([...events.values()], filter)
5275
+ };
5276
+ };
5277
+ var recordVoiceAuditEvent = (store, event) => store.append(createVoiceAuditEvent(event));
5278
+ var recordVoiceProviderAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
5279
+ action: `${input.kind}.provider.call`,
5280
+ actor: input.actor,
5281
+ metadata: input.metadata,
5282
+ outcome: input.outcome,
5283
+ payload: {
5284
+ cost: input.cost,
5285
+ elapsedMs: input.elapsedMs,
5286
+ error: input.error,
5287
+ kind: input.kind,
5288
+ model: input.model,
5289
+ provider: input.provider
5290
+ },
5291
+ resource: {
5292
+ id: input.provider,
5293
+ type: "provider"
5294
+ },
5295
+ sessionId: input.sessionId,
5296
+ traceId: input.traceId,
5297
+ type: "provider.call"
5298
+ });
5299
+ var recordVoiceToolAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
5300
+ action: "tool.call",
5301
+ actor: input.actor,
5302
+ metadata: input.metadata,
5303
+ outcome: input.outcome,
5304
+ payload: {
5305
+ elapsedMs: input.elapsedMs,
5306
+ error: input.error,
5307
+ toolCallId: input.toolCallId,
5308
+ toolName: input.toolName
5309
+ },
5310
+ resource: {
5311
+ id: input.toolName,
5312
+ type: "tool"
5313
+ },
5314
+ sessionId: input.sessionId,
5315
+ traceId: input.traceId,
5316
+ type: "tool.call"
5317
+ });
5318
+ var recordVoiceHandoffAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
5319
+ action: "handoff",
5320
+ actor: input.actor,
5321
+ metadata: input.metadata,
5322
+ outcome: input.outcome,
5323
+ payload: {
5324
+ fromAgentId: input.fromAgentId,
5325
+ reason: input.reason,
5326
+ target: input.target,
5327
+ toAgentId: input.toAgentId
5328
+ },
5329
+ resource: {
5330
+ id: input.toAgentId ?? input.target,
5331
+ type: "handoff"
5332
+ },
5333
+ sessionId: input.sessionId,
5334
+ traceId: input.traceId,
5335
+ type: "handoff"
5336
+ });
5337
+ var recordVoiceRetentionAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
5338
+ action: input.dryRun ? "retention.plan" : "retention.apply",
5339
+ actor: input.actor ?? {
5340
+ id: "voice-retention",
5341
+ kind: "system"
5342
+ },
5343
+ metadata: input.metadata,
5344
+ outcome: "success",
5345
+ payload: {
5346
+ deletedCount: input.report.deletedCount,
5347
+ dryRun: input.dryRun,
5348
+ scopes: input.report.scopes
5349
+ },
5350
+ resource: {
5351
+ type: "retention-policy"
5352
+ },
5353
+ type: "retention.policy"
5354
+ });
5355
+ var recordVoiceOperatorAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
5356
+ action: input.action,
5357
+ actor: input.actor,
5358
+ metadata: input.metadata,
5359
+ outcome: input.outcome ?? "success",
5360
+ payload: input.payload,
5361
+ resource: input.resource,
5362
+ sessionId: input.sessionId,
5363
+ traceId: input.traceId,
5364
+ type: "operator.action"
5365
+ });
5366
+ var createVoiceAuditLogger = (store) => ({
5367
+ handoff: (input) => recordVoiceHandoffAuditEvent({ ...input, store }),
5368
+ operatorAction: (input) => recordVoiceOperatorAuditEvent({ ...input, store }),
5369
+ providerCall: (input) => recordVoiceProviderAuditEvent({ ...input, store }),
5370
+ record: (event) => recordVoiceAuditEvent(store, event),
5371
+ retention: (input) => recordVoiceRetentionAuditEvent({ ...input, store }),
5372
+ toolCall: (input) => recordVoiceToolAuditEvent({ ...input, store })
5373
+ });
5374
+
5375
+ // src/profileSwitchRecommendation.ts
5376
+ var readDefaults = (input) => ("defaults" in input) ? input.defaults : input;
5377
+ var isNumber = (value) => typeof value === "number" && Number.isFinite(value);
5378
+ var exceeds = (observed, budget) => isNumber(observed) && isNumber(budget) && observed > budget;
5379
+ var scoreProfile = (profile, observed) => {
5380
+ const evidence = profile.evidence;
5381
+ const live = evidence.liveP95Ms ?? observed.liveP95Ms ?? 0;
5382
+ const provider = evidence.providerP95Ms ?? observed.providerP95Ms ?? 0;
5383
+ const turn = evidence.turnP95Ms ?? observed.turnP95Ms ?? 0;
5384
+ const statusPenalty = profile.status === "pass" ? 0 : profile.status === "warn" ? 1e4 : 25000;
5385
+ const noisyBonus = (observed.fallbackUsed || (observed.turnWarnings ?? 0) > 0) && /noisy|phone/i.test(`${profile.profileId} ${profile.label ?? ""}`) ? -1000 : 0;
5386
+ return live + provider + turn + statusPenalty + noisyBonus;
5387
+ };
5388
+ var clampConfidence = (value) => Math.max(0, Math.min(0.99, Number(value.toFixed(2))));
5389
+ var estimateSwitchConfidence = (recommendation) => {
5390
+ if (!recommendation.ok || recommendation.status !== "switch") {
5391
+ return recommendation.status === "stay" && recommendation.ok ? 0.99 : 0;
5392
+ }
5393
+ const observed = recommendation.observed;
5394
+ const currentStatus = recommendation.currentProfile?.status;
5395
+ const recommendedStatus = recommendation.recommendedProfile?.status;
5396
+ let confidence = 0.58;
5397
+ if (currentStatus && currentStatus !== "pass") {
5398
+ confidence += 0.12;
5399
+ }
5400
+ if (recommendedStatus === "pass") {
5401
+ confidence += 0.1;
5402
+ }
5403
+ if (observed.fallbackUsed) {
5404
+ confidence += 0.08;
5405
+ }
5406
+ if ((observed.turnWarnings ?? 0) > 0) {
5407
+ confidence += 0.08;
5408
+ }
5409
+ if (recommendation.reasons.some((reason) => /budget|strongest measured fit/i.test(reason))) {
5410
+ confidence += 0.08;
5411
+ }
5412
+ return clampConfidence(confidence);
5413
+ };
5414
+ var recommendVoiceProfileSwitch = (options) => {
5415
+ const defaults = readDefaults(options.defaults);
5416
+ const observed = options.observed ?? {};
5417
+ const currentProfileId = observed.currentProfileId ?? options.defaultProfileId ?? defaults.profiles[0]?.profileId;
5418
+ const currentProfile = defaults.profiles.find((profile) => profile.profileId === currentProfileId);
5419
+ const candidates = defaults.profiles.filter((profile) => profile.status !== "fail");
5420
+ const recommended = candidates.slice().sort((left, right) => scoreProfile(left, observed) - scoreProfile(right, observed))[0];
5421
+ const issues = [
5422
+ ...defaults.profiles.length === 0 ? ["No measured profile defaults are available."] : [],
5423
+ ...!currentProfile && currentProfileId ? [`Current profile ${currentProfileId} is not present in measured defaults.`] : [],
5424
+ ...!recommended ? ["No non-failing measured profile can be recommended."] : []
5425
+ ];
5426
+ const currentOverBudget = currentProfile ? [
5427
+ exceeds(observed.liveP95Ms, currentProfile.latencyBudgets.maxLiveP95Ms) ? "live p95 exceeds this profile budget" : undefined,
5428
+ exceeds(observed.providerP95Ms, currentProfile.latencyBudgets.maxProviderP95Ms) ? "provider p95 exceeds this profile budget" : undefined,
5429
+ exceeds(observed.turnP95Ms, currentProfile.latencyBudgets.maxTurnP95Ms) ? "turn p95 exceeds this profile budget" : undefined
5430
+ ].filter((reason) => Boolean(reason)) : [];
5431
+ const minImprovementMs = options.minImprovementMs ?? 250;
5432
+ const currentScore = currentProfile ? scoreProfile(currentProfile, observed) : Number.POSITIVE_INFINITY;
5433
+ const recommendedScore = recommended ? scoreProfile(recommended, observed) : Number.POSITIVE_INFINITY;
5434
+ const shouldSwitch = Boolean(recommended) && recommended?.profileId !== currentProfile?.profileId && (!currentProfile || currentProfile.status !== "pass" || currentOverBudget.length > 0 || currentScore - recommendedScore >= minImprovementMs);
5435
+ const reasons = [
5436
+ ...currentOverBudget,
5437
+ ...currentProfile?.status && currentProfile.status !== "pass" ? [`current profile is ${currentProfile.status}`] : [],
5438
+ ...observed.fallbackUsed ? ["current session used provider fallback"] : [],
5439
+ ...(observed.turnWarnings ?? 0) > 0 ? [`${observed.turnWarnings} turn quality warning(s) observed`] : [],
5440
+ ...shouldSwitch && recommended ? [
5441
+ `${recommended.label ?? recommended.profileId} has the strongest measured fit for these signals`
5442
+ ] : []
5443
+ ];
5444
+ return {
5445
+ currentProfile: currentProfile ? {
5446
+ label: currentProfile.label,
5447
+ profileId: currentProfile.profileId,
5448
+ status: currentProfile.status
5449
+ } : undefined,
5450
+ generatedAt: new Date().toISOString(),
5451
+ issues,
5452
+ nextMove: issues.length > 0 ? "Collect fresh real-call profile evidence before switching automatically." : shouldSwitch && recommended ? `Switch to ${recommended.label ?? recommended.profileId} for this session profile.` : "Keep the current measured profile unless new session evidence drifts.",
5453
+ ok: issues.length === 0,
5454
+ observed,
5455
+ reasons: reasons.length > 0 ? reasons : ["current profile matches measured defaults and observed budgets"],
5456
+ recommendedProfile: recommended ? {
5457
+ evidence: recommended.evidence,
5458
+ label: recommended.label,
5459
+ latencyBudgets: recommended.latencyBudgets,
5460
+ profileId: recommended.profileId,
5461
+ providerRoutes: recommended.providerRoutes,
5462
+ status: recommended.status
5463
+ } : undefined,
5464
+ status: issues.length > 0 ? "warn" : shouldSwitch ? "switch" : "stay"
5465
+ };
5466
+ };
5467
+ var applyVoiceProfileSwitchGuard = async (options) => {
5468
+ const mode = options.mode ?? "recommend";
5469
+ const minConfidence = options.minConfidence ?? 0.75;
5470
+ const recommendation = recommendVoiceProfileSwitch(options);
5471
+ const confidence = estimateSwitchConfidence(recommendation);
5472
+ const previousProfileId = recommendation.currentProfile?.profileId;
5473
+ const recommendedProfileId = recommendation.recommendedProfile?.profileId;
5474
+ const canSwitch = recommendation.status === "switch" && recommendation.ok && Boolean(recommendedProfileId) && confidence >= minConfidence;
5475
+ const action = recommendation.status === "stay" ? "stay" : canSwitch ? mode === "auto" ? "switch" : "recommend" : "blocked";
5476
+ const selectedProfileId = action === "switch" ? recommendedProfileId : previousProfileId ?? recommendedProfileId;
5477
+ const reason = action === "switch" ? `Auto-switched from ${previousProfileId ?? "unknown"} to ${recommendedProfileId}.` : action === "recommend" ? `Recommended ${recommendedProfileId} but left selection unchanged because mode is recommend.` : action === "blocked" ? `Blocked profile switch because confidence ${confidence} is below ${minConfidence} or evidence is incomplete.` : "Kept current profile because measured evidence does not require a switch.";
5478
+ const decision = {
5479
+ action,
5480
+ autoApplied: action === "switch",
5481
+ confidence,
5482
+ minConfidence,
5483
+ mode,
5484
+ previousProfileId,
5485
+ reason,
5486
+ recommendation,
5487
+ recommendedProfileId,
5488
+ selectedProfileId
5489
+ };
5490
+ if (options.audit) {
5491
+ const auditEvent = await options.audit.append(createVoiceAuditEvent({
5492
+ action: `profile.switch.${action}`,
5493
+ actor: options.actor ?? {
5494
+ id: "absolutejs-voice-profile-switch-guard",
5495
+ kind: "system",
5496
+ name: "AbsoluteJS Voice Profile Switch Guard"
5497
+ },
5498
+ metadata: options.metadata,
5499
+ outcome: action === "blocked" ? "skipped" : "success",
5500
+ payload: {
5501
+ autoApplied: decision.autoApplied,
5502
+ confidence,
5503
+ minConfidence,
5504
+ mode,
5505
+ previousProfileId,
5506
+ reasons: recommendation.reasons,
5507
+ recommendedProfileId,
5508
+ selectedProfileId,
5509
+ status: recommendation.status
5510
+ },
5511
+ resource: {
5512
+ id: selectedProfileId,
5513
+ type: "voice-profile"
5514
+ },
5515
+ sessionId: options.sessionId,
5516
+ traceId: options.traceId,
5517
+ type: "profile.switch"
5518
+ }));
5519
+ decision.auditEvent = auditEvent;
5520
+ }
5521
+ return decision;
5522
+ };
5523
+
5203
5524
  // src/plugin.ts
5204
5525
  var resolveQueryScenario = (query) => {
5205
5526
  if (typeof query?.scenarioId === "string" && query.scenarioId.trim()) {
@@ -5322,6 +5643,7 @@ var resolveSessionId = (runtime, ws) => {
5322
5643
  runtime.socketSessions.set(ws, resolved);
5323
5644
  return resolved;
5324
5645
  };
5646
+ var resolveMaybeFunction = async (value, input) => typeof value === "function" ? await value(input) : value;
5325
5647
  var toAudioChunk = (raw) => {
5326
5648
  if (raw instanceof ArrayBuffer) {
5327
5649
  return raw;
@@ -5395,6 +5717,69 @@ var resolveLexicon = async (config, input) => {
5395
5717
  }
5396
5718
  return normalizeLexicon(config.lexicon);
5397
5719
  };
5720
+ var resolveProfileSwitchGuard = async (config, runtime, input) => {
5721
+ const guard = config.profileSwitchGuard;
5722
+ if (!guard || runtime.profileSwitchGuardedSessions.has(input.sessionId)) {
5723
+ return;
5724
+ }
5725
+ runtime.profileSwitchGuardedSessions.add(input.sessionId);
5726
+ const resolverInput = input;
5727
+ const defaults = await resolveMaybeFunction(guard.defaults, resolverInput);
5728
+ if (!defaults) {
5729
+ throw new Error("voice profileSwitchGuard requires measured profile defaults.");
5730
+ }
5731
+ const observed = await resolveMaybeFunction(guard.observed, resolverInput);
5732
+ const currentProfileId = await resolveMaybeFunction(guard.currentProfileId, resolverInput);
5733
+ const metadata = await resolveMaybeFunction(guard.metadata, resolverInput);
5734
+ const minConfidence = await resolveMaybeFunction(guard.minConfidence, resolverInput);
5735
+ const mode = await resolveMaybeFunction(guard.mode, resolverInput);
5736
+ const decision = await applyVoiceProfileSwitchGuard({
5737
+ actor: guard.actor,
5738
+ audit: guard.audit,
5739
+ defaultProfileId: guard.defaultProfileId,
5740
+ defaults,
5741
+ metadata,
5742
+ minConfidence,
5743
+ mode,
5744
+ observed: {
5745
+ ...observed,
5746
+ currentProfileId: observed?.currentProfileId ?? currentProfileId
5747
+ },
5748
+ sessionId: input.sessionId
5749
+ });
5750
+ await guard.onDecision?.({
5751
+ context: input.context,
5752
+ decision,
5753
+ scenarioId: input.scenarioId,
5754
+ sessionId: input.sessionId
5755
+ });
5756
+ const trace = guard.trace === false ? undefined : guard.trace ?? config.trace;
5757
+ if (trace) {
5758
+ await trace.append({
5759
+ at: Date.now(),
5760
+ metadata: {
5761
+ ...metadata,
5762
+ source: "profile-switch-guard"
5763
+ },
5764
+ payload: {
5765
+ action: decision.action,
5766
+ autoApplied: decision.autoApplied,
5767
+ confidence: decision.confidence,
5768
+ minConfidence: decision.minConfidence,
5769
+ mode: decision.mode,
5770
+ previousProfileId: decision.previousProfileId,
5771
+ reason: decision.reason,
5772
+ recommendedProfileId: decision.recommendedProfileId,
5773
+ selectedProfileId: decision.selectedProfileId,
5774
+ status: decision.recommendation.status
5775
+ },
5776
+ scenarioId: input.scenarioId,
5777
+ sessionId: input.sessionId,
5778
+ type: "provider.decision"
5779
+ });
5780
+ }
5781
+ return decision;
5782
+ };
5398
5783
  var voice = (config) => {
5399
5784
  if (!config.stt && !config.realtime) {
5400
5785
  throw new Error("voice requires either an stt or realtime adapter.");
@@ -5402,6 +5787,7 @@ var voice = (config) => {
5402
5787
  const runtime = {
5403
5788
  activeSessions: new Map,
5404
5789
  logger: resolveLogger(config.logger),
5790
+ profileSwitchGuardedSessions: new Set,
5405
5791
  socketSessions: new WeakMap
5406
5792
  };
5407
5793
  const onTurn = normalizeOnTurn(config.onTurn);
@@ -5413,6 +5799,11 @@ var voice = (config) => {
5413
5799
  const htmxTargets = resolveVoiceHTMXTargets(htmxOptions?.targets);
5414
5800
  const createManagedSession = async (ws, sessionId, scenarioId) => {
5415
5801
  const context = ws.data;
5802
+ const profileSwitchDecision = await resolveProfileSwitchGuard(config, runtime, {
5803
+ context,
5804
+ scenarioId,
5805
+ sessionId
5806
+ });
5416
5807
  const phraseHints = await resolvePhraseHints(config, {
5417
5808
  context,
5418
5809
  scenarioId,
@@ -5470,6 +5861,9 @@ var voice = (config) => {
5470
5861
  onTurn,
5471
5862
  onVoicemail: config.onVoicemail
5472
5863
  },
5864
+ sessionMetadata: profileSwitchDecision && config.profileSwitchGuard?.sessionMetadataKey !== false ? {
5865
+ [config.profileSwitchGuard?.sessionMetadataKey ?? "profileSwitchGuard"]: profileSwitchDecision
5866
+ } : undefined,
5473
5867
  scenarioId,
5474
5868
  socket: createSocketAdapter(ws),
5475
5869
  store: config.session,
@@ -7186,182 +7580,18 @@ var evaluateVoiceCampaignDialerProofEvidence = (report, input = {}) => {
7186
7580
  issues,
7187
7581
  mode: report.mode,
7188
7582
  ok: issues.length === 0,
7189
- providers,
7190
- successfulOutcomes,
7191
- totalProviders: report.providers.length
7192
- };
7193
- };
7194
- var assertVoiceCampaignDialerProofEvidence = (report, input = {}) => {
7195
- const assertion = evaluateVoiceCampaignDialerProofEvidence(report, input);
7196
- if (!assertion.ok) {
7197
- throw new Error(`Voice campaign dialer proof evidence assertion failed: ${assertion.issues.join(" ")}`);
7198
- }
7199
- return assertion;
7200
- };
7201
- // src/audit.ts
7202
- var includes = (filter, value) => {
7203
- if (!filter) {
7204
- return true;
7205
- }
7206
- if (!value) {
7207
- return false;
7208
- }
7209
- return Array.isArray(filter) ? filter.includes(value) : filter === value;
7210
- };
7211
- var createVoiceAuditEvent = (event) => ({
7212
- ...event,
7213
- at: event.at ?? Date.now(),
7214
- id: event.id ?? crypto.randomUUID()
7215
- });
7216
- var filterVoiceAuditEvents = (events, filter = {}) => {
7217
- const sorted = events.filter((event) => {
7218
- if (!includes(filter.type, event.type)) {
7219
- return false;
7220
- }
7221
- if (!includes(filter.outcome, event.outcome)) {
7222
- return false;
7223
- }
7224
- if (filter.actorId && event.actor?.id !== filter.actorId) {
7225
- return false;
7226
- }
7227
- if (filter.resourceId && event.resource?.id !== filter.resourceId) {
7228
- return false;
7229
- }
7230
- if (filter.resourceType && event.resource?.type !== filter.resourceType) {
7231
- return false;
7232
- }
7233
- if (filter.sessionId && event.sessionId !== filter.sessionId) {
7234
- return false;
7235
- }
7236
- if (filter.traceId && event.traceId !== filter.traceId) {
7237
- return false;
7238
- }
7239
- if (typeof filter.after === "number" && event.at <= filter.after) {
7240
- return false;
7241
- }
7242
- if (typeof filter.afterOrAt === "number" && event.at < filter.afterOrAt) {
7243
- return false;
7244
- }
7245
- if (typeof filter.before === "number" && event.at >= filter.before) {
7246
- return false;
7247
- }
7248
- if (typeof filter.beforeOrAt === "number" && event.at > filter.beforeOrAt) {
7249
- return false;
7250
- }
7251
- return true;
7252
- }).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
7253
- return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
7254
- };
7255
- var createVoiceMemoryAuditEventStore = () => {
7256
- const events = new Map;
7257
- return {
7258
- append: (event) => {
7259
- const stored = createVoiceAuditEvent(event);
7260
- events.set(stored.id, stored);
7261
- return stored;
7262
- },
7263
- get: (id) => events.get(id),
7264
- list: (filter) => filterVoiceAuditEvents([...events.values()], filter)
7583
+ providers,
7584
+ successfulOutcomes,
7585
+ totalProviders: report.providers.length
7265
7586
  };
7266
7587
  };
7267
- var recordVoiceAuditEvent = (store, event) => store.append(createVoiceAuditEvent(event));
7268
- var recordVoiceProviderAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
7269
- action: `${input.kind}.provider.call`,
7270
- actor: input.actor,
7271
- metadata: input.metadata,
7272
- outcome: input.outcome,
7273
- payload: {
7274
- cost: input.cost,
7275
- elapsedMs: input.elapsedMs,
7276
- error: input.error,
7277
- kind: input.kind,
7278
- model: input.model,
7279
- provider: input.provider
7280
- },
7281
- resource: {
7282
- id: input.provider,
7283
- type: "provider"
7284
- },
7285
- sessionId: input.sessionId,
7286
- traceId: input.traceId,
7287
- type: "provider.call"
7288
- });
7289
- var recordVoiceToolAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
7290
- action: "tool.call",
7291
- actor: input.actor,
7292
- metadata: input.metadata,
7293
- outcome: input.outcome,
7294
- payload: {
7295
- elapsedMs: input.elapsedMs,
7296
- error: input.error,
7297
- toolCallId: input.toolCallId,
7298
- toolName: input.toolName
7299
- },
7300
- resource: {
7301
- id: input.toolName,
7302
- type: "tool"
7303
- },
7304
- sessionId: input.sessionId,
7305
- traceId: input.traceId,
7306
- type: "tool.call"
7307
- });
7308
- var recordVoiceHandoffAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
7309
- action: "handoff",
7310
- actor: input.actor,
7311
- metadata: input.metadata,
7312
- outcome: input.outcome,
7313
- payload: {
7314
- fromAgentId: input.fromAgentId,
7315
- reason: input.reason,
7316
- target: input.target,
7317
- toAgentId: input.toAgentId
7318
- },
7319
- resource: {
7320
- id: input.toAgentId ?? input.target,
7321
- type: "handoff"
7322
- },
7323
- sessionId: input.sessionId,
7324
- traceId: input.traceId,
7325
- type: "handoff"
7326
- });
7327
- var recordVoiceRetentionAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
7328
- action: input.dryRun ? "retention.plan" : "retention.apply",
7329
- actor: input.actor ?? {
7330
- id: "voice-retention",
7331
- kind: "system"
7332
- },
7333
- metadata: input.metadata,
7334
- outcome: "success",
7335
- payload: {
7336
- deletedCount: input.report.deletedCount,
7337
- dryRun: input.dryRun,
7338
- scopes: input.report.scopes
7339
- },
7340
- resource: {
7341
- type: "retention-policy"
7342
- },
7343
- type: "retention.policy"
7344
- });
7345
- var recordVoiceOperatorAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
7346
- action: input.action,
7347
- actor: input.actor,
7348
- metadata: input.metadata,
7349
- outcome: input.outcome ?? "success",
7350
- payload: input.payload,
7351
- resource: input.resource,
7352
- sessionId: input.sessionId,
7353
- traceId: input.traceId,
7354
- type: "operator.action"
7355
- });
7356
- var createVoiceAuditLogger = (store) => ({
7357
- handoff: (input) => recordVoiceHandoffAuditEvent({ ...input, store }),
7358
- operatorAction: (input) => recordVoiceOperatorAuditEvent({ ...input, store }),
7359
- providerCall: (input) => recordVoiceProviderAuditEvent({ ...input, store }),
7360
- record: (event) => recordVoiceAuditEvent(store, event),
7361
- retention: (input) => recordVoiceRetentionAuditEvent({ ...input, store }),
7362
- toolCall: (input) => recordVoiceToolAuditEvent({ ...input, store })
7363
- });
7364
-
7588
+ var assertVoiceCampaignDialerProofEvidence = (report, input = {}) => {
7589
+ const assertion = evaluateVoiceCampaignDialerProofEvidence(report, input);
7590
+ if (!assertion.ok) {
7591
+ throw new Error(`Voice campaign dialer proof evidence assertion failed: ${assertion.issues.join(" ")}`);
7592
+ }
7593
+ return assertion;
7594
+ };
7365
7595
  // src/agent.ts
7366
7596
  var normalizeText3 = (value) => typeof value === "string" ? value.trim() : "";
7367
7597
  var toErrorMessage3 = (error) => error instanceof Error ? error.message : String(error);
@@ -15694,154 +15924,6 @@ var formatVoiceProofTrendAge = (ageMs) => {
15694
15924
  const days = Math.floor(hours / 24);
15695
15925
  return `${days}d ${hours % 24}h`;
15696
15926
  };
15697
- // src/profileSwitchRecommendation.ts
15698
- var readDefaults = (input) => ("defaults" in input) ? input.defaults : input;
15699
- var isNumber = (value) => typeof value === "number" && Number.isFinite(value);
15700
- var exceeds = (observed, budget) => isNumber(observed) && isNumber(budget) && observed > budget;
15701
- var scoreProfile = (profile, observed) => {
15702
- const evidence = profile.evidence;
15703
- const live = evidence.liveP95Ms ?? observed.liveP95Ms ?? 0;
15704
- const provider = evidence.providerP95Ms ?? observed.providerP95Ms ?? 0;
15705
- const turn = evidence.turnP95Ms ?? observed.turnP95Ms ?? 0;
15706
- const statusPenalty = profile.status === "pass" ? 0 : profile.status === "warn" ? 1e4 : 25000;
15707
- const noisyBonus = (observed.fallbackUsed || (observed.turnWarnings ?? 0) > 0) && /noisy|phone/i.test(`${profile.profileId} ${profile.label ?? ""}`) ? -1000 : 0;
15708
- return live + provider + turn + statusPenalty + noisyBonus;
15709
- };
15710
- var clampConfidence = (value) => Math.max(0, Math.min(0.99, Number(value.toFixed(2))));
15711
- var estimateSwitchConfidence = (recommendation) => {
15712
- if (!recommendation.ok || recommendation.status !== "switch") {
15713
- return recommendation.status === "stay" && recommendation.ok ? 0.99 : 0;
15714
- }
15715
- const observed = recommendation.observed;
15716
- const currentStatus = recommendation.currentProfile?.status;
15717
- const recommendedStatus = recommendation.recommendedProfile?.status;
15718
- let confidence = 0.58;
15719
- if (currentStatus && currentStatus !== "pass") {
15720
- confidence += 0.12;
15721
- }
15722
- if (recommendedStatus === "pass") {
15723
- confidence += 0.1;
15724
- }
15725
- if (observed.fallbackUsed) {
15726
- confidence += 0.08;
15727
- }
15728
- if ((observed.turnWarnings ?? 0) > 0) {
15729
- confidence += 0.08;
15730
- }
15731
- if (recommendation.reasons.some((reason) => /budget|strongest measured fit/i.test(reason))) {
15732
- confidence += 0.08;
15733
- }
15734
- return clampConfidence(confidence);
15735
- };
15736
- var recommendVoiceProfileSwitch = (options) => {
15737
- const defaults = readDefaults(options.defaults);
15738
- const observed = options.observed ?? {};
15739
- const currentProfileId = observed.currentProfileId ?? options.defaultProfileId ?? defaults.profiles[0]?.profileId;
15740
- const currentProfile = defaults.profiles.find((profile) => profile.profileId === currentProfileId);
15741
- const candidates = defaults.profiles.filter((profile) => profile.status !== "fail");
15742
- const recommended = candidates.slice().sort((left, right) => scoreProfile(left, observed) - scoreProfile(right, observed))[0];
15743
- const issues = [
15744
- ...defaults.profiles.length === 0 ? ["No measured profile defaults are available."] : [],
15745
- ...!currentProfile && currentProfileId ? [`Current profile ${currentProfileId} is not present in measured defaults.`] : [],
15746
- ...!recommended ? ["No non-failing measured profile can be recommended."] : []
15747
- ];
15748
- const currentOverBudget = currentProfile ? [
15749
- exceeds(observed.liveP95Ms, currentProfile.latencyBudgets.maxLiveP95Ms) ? "live p95 exceeds this profile budget" : undefined,
15750
- exceeds(observed.providerP95Ms, currentProfile.latencyBudgets.maxProviderP95Ms) ? "provider p95 exceeds this profile budget" : undefined,
15751
- exceeds(observed.turnP95Ms, currentProfile.latencyBudgets.maxTurnP95Ms) ? "turn p95 exceeds this profile budget" : undefined
15752
- ].filter((reason) => Boolean(reason)) : [];
15753
- const minImprovementMs = options.minImprovementMs ?? 250;
15754
- const currentScore = currentProfile ? scoreProfile(currentProfile, observed) : Number.POSITIVE_INFINITY;
15755
- const recommendedScore = recommended ? scoreProfile(recommended, observed) : Number.POSITIVE_INFINITY;
15756
- const shouldSwitch = Boolean(recommended) && recommended?.profileId !== currentProfile?.profileId && (!currentProfile || currentProfile.status !== "pass" || currentOverBudget.length > 0 || currentScore - recommendedScore >= minImprovementMs);
15757
- const reasons = [
15758
- ...currentOverBudget,
15759
- ...currentProfile?.status && currentProfile.status !== "pass" ? [`current profile is ${currentProfile.status}`] : [],
15760
- ...observed.fallbackUsed ? ["current session used provider fallback"] : [],
15761
- ...(observed.turnWarnings ?? 0) > 0 ? [`${observed.turnWarnings} turn quality warning(s) observed`] : [],
15762
- ...shouldSwitch && recommended ? [
15763
- `${recommended.label ?? recommended.profileId} has the strongest measured fit for these signals`
15764
- ] : []
15765
- ];
15766
- return {
15767
- currentProfile: currentProfile ? {
15768
- label: currentProfile.label,
15769
- profileId: currentProfile.profileId,
15770
- status: currentProfile.status
15771
- } : undefined,
15772
- generatedAt: new Date().toISOString(),
15773
- issues,
15774
- nextMove: issues.length > 0 ? "Collect fresh real-call profile evidence before switching automatically." : shouldSwitch && recommended ? `Switch to ${recommended.label ?? recommended.profileId} for this session profile.` : "Keep the current measured profile unless new session evidence drifts.",
15775
- ok: issues.length === 0,
15776
- observed,
15777
- reasons: reasons.length > 0 ? reasons : ["current profile matches measured defaults and observed budgets"],
15778
- recommendedProfile: recommended ? {
15779
- evidence: recommended.evidence,
15780
- label: recommended.label,
15781
- latencyBudgets: recommended.latencyBudgets,
15782
- profileId: recommended.profileId,
15783
- providerRoutes: recommended.providerRoutes,
15784
- status: recommended.status
15785
- } : undefined,
15786
- status: issues.length > 0 ? "warn" : shouldSwitch ? "switch" : "stay"
15787
- };
15788
- };
15789
- var applyVoiceProfileSwitchGuard = async (options) => {
15790
- const mode = options.mode ?? "recommend";
15791
- const minConfidence = options.minConfidence ?? 0.75;
15792
- const recommendation = recommendVoiceProfileSwitch(options);
15793
- const confidence = estimateSwitchConfidence(recommendation);
15794
- const previousProfileId = recommendation.currentProfile?.profileId;
15795
- const recommendedProfileId = recommendation.recommendedProfile?.profileId;
15796
- const canSwitch = recommendation.status === "switch" && recommendation.ok && Boolean(recommendedProfileId) && confidence >= minConfidence;
15797
- const action = recommendation.status === "stay" ? "stay" : canSwitch ? mode === "auto" ? "switch" : "recommend" : "blocked";
15798
- const selectedProfileId = action === "switch" ? recommendedProfileId : previousProfileId ?? recommendedProfileId;
15799
- const reason = action === "switch" ? `Auto-switched from ${previousProfileId ?? "unknown"} to ${recommendedProfileId}.` : action === "recommend" ? `Recommended ${recommendedProfileId} but left selection unchanged because mode is recommend.` : action === "blocked" ? `Blocked profile switch because confidence ${confidence} is below ${minConfidence} or evidence is incomplete.` : "Kept current profile because measured evidence does not require a switch.";
15800
- const decision = {
15801
- action,
15802
- autoApplied: action === "switch",
15803
- confidence,
15804
- minConfidence,
15805
- mode,
15806
- previousProfileId,
15807
- reason,
15808
- recommendation,
15809
- recommendedProfileId,
15810
- selectedProfileId
15811
- };
15812
- if (options.audit) {
15813
- const auditEvent = await options.audit.append(createVoiceAuditEvent({
15814
- action: `profile.switch.${action}`,
15815
- actor: options.actor ?? {
15816
- id: "absolutejs-voice-profile-switch-guard",
15817
- kind: "system",
15818
- name: "AbsoluteJS Voice Profile Switch Guard"
15819
- },
15820
- metadata: options.metadata,
15821
- outcome: action === "blocked" ? "skipped" : "success",
15822
- payload: {
15823
- autoApplied: decision.autoApplied,
15824
- confidence,
15825
- minConfidence,
15826
- mode,
15827
- previousProfileId,
15828
- reasons: recommendation.reasons,
15829
- recommendedProfileId,
15830
- selectedProfileId,
15831
- status: recommendation.status
15832
- },
15833
- resource: {
15834
- id: selectedProfileId,
15835
- type: "voice-profile"
15836
- },
15837
- sessionId: options.sessionId,
15838
- traceId: options.traceId,
15839
- type: "profile.switch"
15840
- }));
15841
- decision.auditEvent = auditEvent;
15842
- }
15843
- return decision;
15844
- };
15845
15927
  // src/providerDecisionTraces.ts
15846
15928
  import { Elysia as Elysia23 } from "elysia";
15847
15929