@absolutejs/voice 0.0.22-beta.355 → 0.0.22-beta.357
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/audit.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +393 -230
- package/dist/profileSwitchRecommendation.d.ts +26 -0
- package/dist/testing/index.js +6 -0
- package/dist/types.d.ts +29 -0
- package/dist/vue/useVoiceReadinessFailures.d.ts +2 -0
- package/package.json +1 -1
package/dist/audit.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type VoiceAuditEventType = 'handoff' | 'operator.action' | 'provider.call' | 'retention.policy' | 'tool.call';
|
|
1
|
+
export type VoiceAuditEventType = 'handoff' | 'operator.action' | 'profile.switch' | 'provider.call' | 'retention.policy' | 'tool.call';
|
|
2
2
|
export type VoiceAuditActor = {
|
|
3
3
|
id: string;
|
|
4
4
|
kind: 'agent' | 'operator' | 'system' | 'worker';
|
package/dist/index.d.ts
CHANGED
|
@@ -31,8 +31,8 @@ export { assertVoiceCompetitiveCoverage, buildVoiceCompetitiveCoverageReport, cr
|
|
|
31
31
|
export type { VoiceCompetitiveCoverageAssertionInput, VoiceCompetitiveCoverageAssertionReport, VoiceCompetitiveCoverageIssue, VoiceCompetitiveCoverageLevel, VoiceCompetitiveCoverageReport, VoiceCompetitiveCoverageReportInput, VoiceCompetitiveCoverageRoutesOptions, VoiceCompetitiveCoverageStatus, VoiceCompetitiveCoverageSummary, VoiceCompetitiveDepthLevel, VoiceCompetitiveEvidence, VoiceCompetitiveSurface } from './competitiveCoverage';
|
|
32
32
|
export type { VoicePlatformCoverageAssertionInput, VoicePlatformCoverageAssertionReport, VoicePlatformCoverageEvidence, VoicePlatformCoverageRoutesOptions, VoicePlatformCoverageStatus, VoicePlatformCoverageSummary, VoicePlatformCoverageSummaryInput, VoicePlatformCoverageSurface } from './platformCoverage';
|
|
33
33
|
export { assertVoiceProofTrendEvidence, buildEmptyVoiceProofTrendReport, buildVoiceProofTrendProfileSummaries, buildVoiceProofTrendRecommendationReport, buildVoiceProofTrendReportFromRealCallProfiles, buildVoiceProofTrendReport, buildVoiceRealCallProfileDefaults, buildVoiceRealCallProfileHistoryReport, createVoiceProofTrendRecommendationRoutes, createVoiceProofTrendRoutes, createVoiceRealCallProfileHistoryRoutes, DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS, DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS, evaluateVoiceProofTrendEvidence, formatVoiceProofTrendAge, normalizeVoiceProofTrendReport, readVoiceProofTrendReportFile, renderVoiceProofTrendRecommendationHTML, renderVoiceProofTrendRecommendationMarkdown, renderVoiceRealCallProfileHistoryHTML, renderVoiceRealCallProfileHistoryMarkdown, resolveVoiceRealCallProfileProviderRoute } from './proofTrends';
|
|
34
|
-
export { recommendVoiceProfileSwitch } from './profileSwitchRecommendation';
|
|
35
|
-
export type { VoiceProfileSwitchObservedSignals, VoiceProfileSwitchRecommendation, VoiceProfileSwitchRecommendationOptions } from './profileSwitchRecommendation';
|
|
34
|
+
export { applyVoiceProfileSwitchGuard, recommendVoiceProfileSwitch } from './profileSwitchRecommendation';
|
|
35
|
+
export type { VoiceProfileSwitchGuardAction, VoiceProfileSwitchGuardDecision, VoiceProfileSwitchGuardMode, VoiceProfileSwitchGuardOptions, VoiceProfileSwitchObservedSignals, VoiceProfileSwitchRecommendation, VoiceProfileSwitchRecommendationOptions } from './profileSwitchRecommendation';
|
|
36
36
|
export { buildVoiceProviderDecisionTraceReport, createVoiceProviderDecisionTraceEvent, createVoiceProviderDecisionTraceRoutes, listVoiceProviderDecisionTraces, renderVoiceProviderDecisionTraceHTML, renderVoiceProviderDecisionTraceMarkdown } from './providerDecisionTraces';
|
|
37
37
|
export type { VoiceProviderDecisionStatus, VoiceProviderDecisionSurfaceReport, VoiceProviderDecisionTrace, VoiceProviderDecisionTraceInput, VoiceProviderDecisionTraceIssue, VoiceProviderDecisionTraceReport, VoiceProviderDecisionTraceReportOptions, VoiceProviderDecisionTraceRoutesOptions } from './providerDecisionTraces';
|
|
38
38
|
export type { VoiceProofTrendAssertionInput, VoiceProofTrendAssertionReport, VoiceProofTrendCycle, VoiceProofTrendProfileDefinition, VoiceProofTrendProfileRecommendation, VoiceProofTrendProfileSummaryOptions, VoiceProofTrendProfileSummary, VoiceProofTrendProviderRecommendation, VoiceProofTrendProviderSummary, VoiceProofTrendRecommendation, VoiceProofTrendRecommendationOptions, VoiceProofTrendRecommendationReport, VoiceProofTrendRecommendationRoutesOptions, VoiceProofTrendRecommendationStatus, VoiceProofTrendRecommendationSurface, VoiceProofTrendRealCallProfileEvidence, VoiceProofTrendRealCallProfileReportOptions, VoiceProofTrendReport, VoiceProofTrendReportInput, VoiceProofTrendRoutesOptions, VoiceProofTrendRuntimeChannelSummary, VoiceProofTrendStatus, VoiceProofTrendSummary, VoiceRealCallProfileDefault, VoiceRealCallProfileDefaultsOptions, VoiceRealCallProfileDefaultsReport, VoiceRealCallProfileHistoryOptions, VoiceRealCallProfileHistoryReport, VoiceRealCallProfileHistoryRoutesOptions, VoiceRealCallProfileProviderRouteOptions } from './proofTrends';
|
package/dist/index.js
CHANGED
|
@@ -4994,6 +4994,12 @@ var createVoiceSession = (options) => {
|
|
|
4994
4994
|
if (options.scenarioId && session.scenarioId !== options.scenarioId) {
|
|
4995
4995
|
session.scenarioId = options.scenarioId;
|
|
4996
4996
|
}
|
|
4997
|
+
if (options.sessionMetadata) {
|
|
4998
|
+
session.metadata = {
|
|
4999
|
+
...session.metadata && typeof session.metadata === "object" ? session.metadata : {},
|
|
5000
|
+
...options.sessionMetadata
|
|
5001
|
+
};
|
|
5002
|
+
}
|
|
4997
5003
|
ensureCommittedTurnGuard(session);
|
|
4998
5004
|
let shouldFireOnSession = !existingSession;
|
|
4999
5005
|
if (existingSession?.scenarioId && options.scenarioId && existingSession.scenarioId !== options.scenarioId) {
|
|
@@ -5200,6 +5206,319 @@ var createVoiceSession = (options) => {
|
|
|
5200
5206
|
return api;
|
|
5201
5207
|
};
|
|
5202
5208
|
|
|
5209
|
+
// src/audit.ts
|
|
5210
|
+
var includes = (filter, value) => {
|
|
5211
|
+
if (!filter) {
|
|
5212
|
+
return true;
|
|
5213
|
+
}
|
|
5214
|
+
if (!value) {
|
|
5215
|
+
return false;
|
|
5216
|
+
}
|
|
5217
|
+
return Array.isArray(filter) ? filter.includes(value) : filter === value;
|
|
5218
|
+
};
|
|
5219
|
+
var createVoiceAuditEvent = (event) => ({
|
|
5220
|
+
...event,
|
|
5221
|
+
at: event.at ?? Date.now(),
|
|
5222
|
+
id: event.id ?? crypto.randomUUID()
|
|
5223
|
+
});
|
|
5224
|
+
var filterVoiceAuditEvents = (events, filter = {}) => {
|
|
5225
|
+
const sorted = events.filter((event) => {
|
|
5226
|
+
if (!includes(filter.type, event.type)) {
|
|
5227
|
+
return false;
|
|
5228
|
+
}
|
|
5229
|
+
if (!includes(filter.outcome, event.outcome)) {
|
|
5230
|
+
return false;
|
|
5231
|
+
}
|
|
5232
|
+
if (filter.actorId && event.actor?.id !== filter.actorId) {
|
|
5233
|
+
return false;
|
|
5234
|
+
}
|
|
5235
|
+
if (filter.resourceId && event.resource?.id !== filter.resourceId) {
|
|
5236
|
+
return false;
|
|
5237
|
+
}
|
|
5238
|
+
if (filter.resourceType && event.resource?.type !== filter.resourceType) {
|
|
5239
|
+
return false;
|
|
5240
|
+
}
|
|
5241
|
+
if (filter.sessionId && event.sessionId !== filter.sessionId) {
|
|
5242
|
+
return false;
|
|
5243
|
+
}
|
|
5244
|
+
if (filter.traceId && event.traceId !== filter.traceId) {
|
|
5245
|
+
return false;
|
|
5246
|
+
}
|
|
5247
|
+
if (typeof filter.after === "number" && event.at <= filter.after) {
|
|
5248
|
+
return false;
|
|
5249
|
+
}
|
|
5250
|
+
if (typeof filter.afterOrAt === "number" && event.at < filter.afterOrAt) {
|
|
5251
|
+
return false;
|
|
5252
|
+
}
|
|
5253
|
+
if (typeof filter.before === "number" && event.at >= filter.before) {
|
|
5254
|
+
return false;
|
|
5255
|
+
}
|
|
5256
|
+
if (typeof filter.beforeOrAt === "number" && event.at > filter.beforeOrAt) {
|
|
5257
|
+
return false;
|
|
5258
|
+
}
|
|
5259
|
+
return true;
|
|
5260
|
+
}).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
|
|
5261
|
+
return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
|
|
5262
|
+
};
|
|
5263
|
+
var createVoiceMemoryAuditEventStore = () => {
|
|
5264
|
+
const events = new Map;
|
|
5265
|
+
return {
|
|
5266
|
+
append: (event) => {
|
|
5267
|
+
const stored = createVoiceAuditEvent(event);
|
|
5268
|
+
events.set(stored.id, stored);
|
|
5269
|
+
return stored;
|
|
5270
|
+
},
|
|
5271
|
+
get: (id) => events.get(id),
|
|
5272
|
+
list: (filter) => filterVoiceAuditEvents([...events.values()], filter)
|
|
5273
|
+
};
|
|
5274
|
+
};
|
|
5275
|
+
var recordVoiceAuditEvent = (store, event) => store.append(createVoiceAuditEvent(event));
|
|
5276
|
+
var recordVoiceProviderAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
|
|
5277
|
+
action: `${input.kind}.provider.call`,
|
|
5278
|
+
actor: input.actor,
|
|
5279
|
+
metadata: input.metadata,
|
|
5280
|
+
outcome: input.outcome,
|
|
5281
|
+
payload: {
|
|
5282
|
+
cost: input.cost,
|
|
5283
|
+
elapsedMs: input.elapsedMs,
|
|
5284
|
+
error: input.error,
|
|
5285
|
+
kind: input.kind,
|
|
5286
|
+
model: input.model,
|
|
5287
|
+
provider: input.provider
|
|
5288
|
+
},
|
|
5289
|
+
resource: {
|
|
5290
|
+
id: input.provider,
|
|
5291
|
+
type: "provider"
|
|
5292
|
+
},
|
|
5293
|
+
sessionId: input.sessionId,
|
|
5294
|
+
traceId: input.traceId,
|
|
5295
|
+
type: "provider.call"
|
|
5296
|
+
});
|
|
5297
|
+
var recordVoiceToolAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
|
|
5298
|
+
action: "tool.call",
|
|
5299
|
+
actor: input.actor,
|
|
5300
|
+
metadata: input.metadata,
|
|
5301
|
+
outcome: input.outcome,
|
|
5302
|
+
payload: {
|
|
5303
|
+
elapsedMs: input.elapsedMs,
|
|
5304
|
+
error: input.error,
|
|
5305
|
+
toolCallId: input.toolCallId,
|
|
5306
|
+
toolName: input.toolName
|
|
5307
|
+
},
|
|
5308
|
+
resource: {
|
|
5309
|
+
id: input.toolName,
|
|
5310
|
+
type: "tool"
|
|
5311
|
+
},
|
|
5312
|
+
sessionId: input.sessionId,
|
|
5313
|
+
traceId: input.traceId,
|
|
5314
|
+
type: "tool.call"
|
|
5315
|
+
});
|
|
5316
|
+
var recordVoiceHandoffAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
|
|
5317
|
+
action: "handoff",
|
|
5318
|
+
actor: input.actor,
|
|
5319
|
+
metadata: input.metadata,
|
|
5320
|
+
outcome: input.outcome,
|
|
5321
|
+
payload: {
|
|
5322
|
+
fromAgentId: input.fromAgentId,
|
|
5323
|
+
reason: input.reason,
|
|
5324
|
+
target: input.target,
|
|
5325
|
+
toAgentId: input.toAgentId
|
|
5326
|
+
},
|
|
5327
|
+
resource: {
|
|
5328
|
+
id: input.toAgentId ?? input.target,
|
|
5329
|
+
type: "handoff"
|
|
5330
|
+
},
|
|
5331
|
+
sessionId: input.sessionId,
|
|
5332
|
+
traceId: input.traceId,
|
|
5333
|
+
type: "handoff"
|
|
5334
|
+
});
|
|
5335
|
+
var recordVoiceRetentionAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
|
|
5336
|
+
action: input.dryRun ? "retention.plan" : "retention.apply",
|
|
5337
|
+
actor: input.actor ?? {
|
|
5338
|
+
id: "voice-retention",
|
|
5339
|
+
kind: "system"
|
|
5340
|
+
},
|
|
5341
|
+
metadata: input.metadata,
|
|
5342
|
+
outcome: "success",
|
|
5343
|
+
payload: {
|
|
5344
|
+
deletedCount: input.report.deletedCount,
|
|
5345
|
+
dryRun: input.dryRun,
|
|
5346
|
+
scopes: input.report.scopes
|
|
5347
|
+
},
|
|
5348
|
+
resource: {
|
|
5349
|
+
type: "retention-policy"
|
|
5350
|
+
},
|
|
5351
|
+
type: "retention.policy"
|
|
5352
|
+
});
|
|
5353
|
+
var recordVoiceOperatorAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
|
|
5354
|
+
action: input.action,
|
|
5355
|
+
actor: input.actor,
|
|
5356
|
+
metadata: input.metadata,
|
|
5357
|
+
outcome: input.outcome ?? "success",
|
|
5358
|
+
payload: input.payload,
|
|
5359
|
+
resource: input.resource,
|
|
5360
|
+
sessionId: input.sessionId,
|
|
5361
|
+
traceId: input.traceId,
|
|
5362
|
+
type: "operator.action"
|
|
5363
|
+
});
|
|
5364
|
+
var createVoiceAuditLogger = (store) => ({
|
|
5365
|
+
handoff: (input) => recordVoiceHandoffAuditEvent({ ...input, store }),
|
|
5366
|
+
operatorAction: (input) => recordVoiceOperatorAuditEvent({ ...input, store }),
|
|
5367
|
+
providerCall: (input) => recordVoiceProviderAuditEvent({ ...input, store }),
|
|
5368
|
+
record: (event) => recordVoiceAuditEvent(store, event),
|
|
5369
|
+
retention: (input) => recordVoiceRetentionAuditEvent({ ...input, store }),
|
|
5370
|
+
toolCall: (input) => recordVoiceToolAuditEvent({ ...input, store })
|
|
5371
|
+
});
|
|
5372
|
+
|
|
5373
|
+
// src/profileSwitchRecommendation.ts
|
|
5374
|
+
var readDefaults = (input) => ("defaults" in input) ? input.defaults : input;
|
|
5375
|
+
var isNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
5376
|
+
var exceeds = (observed, budget) => isNumber(observed) && isNumber(budget) && observed > budget;
|
|
5377
|
+
var scoreProfile = (profile, observed) => {
|
|
5378
|
+
const evidence = profile.evidence;
|
|
5379
|
+
const live = evidence.liveP95Ms ?? observed.liveP95Ms ?? 0;
|
|
5380
|
+
const provider = evidence.providerP95Ms ?? observed.providerP95Ms ?? 0;
|
|
5381
|
+
const turn = evidence.turnP95Ms ?? observed.turnP95Ms ?? 0;
|
|
5382
|
+
const statusPenalty = profile.status === "pass" ? 0 : profile.status === "warn" ? 1e4 : 25000;
|
|
5383
|
+
const noisyBonus = (observed.fallbackUsed || (observed.turnWarnings ?? 0) > 0) && /noisy|phone/i.test(`${profile.profileId} ${profile.label ?? ""}`) ? -1000 : 0;
|
|
5384
|
+
return live + provider + turn + statusPenalty + noisyBonus;
|
|
5385
|
+
};
|
|
5386
|
+
var clampConfidence = (value) => Math.max(0, Math.min(0.99, Number(value.toFixed(2))));
|
|
5387
|
+
var estimateSwitchConfidence = (recommendation) => {
|
|
5388
|
+
if (!recommendation.ok || recommendation.status !== "switch") {
|
|
5389
|
+
return recommendation.status === "stay" && recommendation.ok ? 0.99 : 0;
|
|
5390
|
+
}
|
|
5391
|
+
const observed = recommendation.observed;
|
|
5392
|
+
const currentStatus = recommendation.currentProfile?.status;
|
|
5393
|
+
const recommendedStatus = recommendation.recommendedProfile?.status;
|
|
5394
|
+
let confidence = 0.58;
|
|
5395
|
+
if (currentStatus && currentStatus !== "pass") {
|
|
5396
|
+
confidence += 0.12;
|
|
5397
|
+
}
|
|
5398
|
+
if (recommendedStatus === "pass") {
|
|
5399
|
+
confidence += 0.1;
|
|
5400
|
+
}
|
|
5401
|
+
if (observed.fallbackUsed) {
|
|
5402
|
+
confidence += 0.08;
|
|
5403
|
+
}
|
|
5404
|
+
if ((observed.turnWarnings ?? 0) > 0) {
|
|
5405
|
+
confidence += 0.08;
|
|
5406
|
+
}
|
|
5407
|
+
if (recommendation.reasons.some((reason) => /budget|strongest measured fit/i.test(reason))) {
|
|
5408
|
+
confidence += 0.08;
|
|
5409
|
+
}
|
|
5410
|
+
return clampConfidence(confidence);
|
|
5411
|
+
};
|
|
5412
|
+
var recommendVoiceProfileSwitch = (options) => {
|
|
5413
|
+
const defaults = readDefaults(options.defaults);
|
|
5414
|
+
const observed = options.observed ?? {};
|
|
5415
|
+
const currentProfileId = observed.currentProfileId ?? options.defaultProfileId ?? defaults.profiles[0]?.profileId;
|
|
5416
|
+
const currentProfile = defaults.profiles.find((profile) => profile.profileId === currentProfileId);
|
|
5417
|
+
const candidates = defaults.profiles.filter((profile) => profile.status !== "fail");
|
|
5418
|
+
const recommended = candidates.slice().sort((left, right) => scoreProfile(left, observed) - scoreProfile(right, observed))[0];
|
|
5419
|
+
const issues = [
|
|
5420
|
+
...defaults.profiles.length === 0 ? ["No measured profile defaults are available."] : [],
|
|
5421
|
+
...!currentProfile && currentProfileId ? [`Current profile ${currentProfileId} is not present in measured defaults.`] : [],
|
|
5422
|
+
...!recommended ? ["No non-failing measured profile can be recommended."] : []
|
|
5423
|
+
];
|
|
5424
|
+
const currentOverBudget = currentProfile ? [
|
|
5425
|
+
exceeds(observed.liveP95Ms, currentProfile.latencyBudgets.maxLiveP95Ms) ? "live p95 exceeds this profile budget" : undefined,
|
|
5426
|
+
exceeds(observed.providerP95Ms, currentProfile.latencyBudgets.maxProviderP95Ms) ? "provider p95 exceeds this profile budget" : undefined,
|
|
5427
|
+
exceeds(observed.turnP95Ms, currentProfile.latencyBudgets.maxTurnP95Ms) ? "turn p95 exceeds this profile budget" : undefined
|
|
5428
|
+
].filter((reason) => Boolean(reason)) : [];
|
|
5429
|
+
const minImprovementMs = options.minImprovementMs ?? 250;
|
|
5430
|
+
const currentScore = currentProfile ? scoreProfile(currentProfile, observed) : Number.POSITIVE_INFINITY;
|
|
5431
|
+
const recommendedScore = recommended ? scoreProfile(recommended, observed) : Number.POSITIVE_INFINITY;
|
|
5432
|
+
const shouldSwitch = Boolean(recommended) && recommended?.profileId !== currentProfile?.profileId && (!currentProfile || currentProfile.status !== "pass" || currentOverBudget.length > 0 || currentScore - recommendedScore >= minImprovementMs);
|
|
5433
|
+
const reasons = [
|
|
5434
|
+
...currentOverBudget,
|
|
5435
|
+
...currentProfile?.status && currentProfile.status !== "pass" ? [`current profile is ${currentProfile.status}`] : [],
|
|
5436
|
+
...observed.fallbackUsed ? ["current session used provider fallback"] : [],
|
|
5437
|
+
...(observed.turnWarnings ?? 0) > 0 ? [`${observed.turnWarnings} turn quality warning(s) observed`] : [],
|
|
5438
|
+
...shouldSwitch && recommended ? [
|
|
5439
|
+
`${recommended.label ?? recommended.profileId} has the strongest measured fit for these signals`
|
|
5440
|
+
] : []
|
|
5441
|
+
];
|
|
5442
|
+
return {
|
|
5443
|
+
currentProfile: currentProfile ? {
|
|
5444
|
+
label: currentProfile.label,
|
|
5445
|
+
profileId: currentProfile.profileId,
|
|
5446
|
+
status: currentProfile.status
|
|
5447
|
+
} : undefined,
|
|
5448
|
+
generatedAt: new Date().toISOString(),
|
|
5449
|
+
issues,
|
|
5450
|
+
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.",
|
|
5451
|
+
ok: issues.length === 0,
|
|
5452
|
+
observed,
|
|
5453
|
+
reasons: reasons.length > 0 ? reasons : ["current profile matches measured defaults and observed budgets"],
|
|
5454
|
+
recommendedProfile: recommended ? {
|
|
5455
|
+
evidence: recommended.evidence,
|
|
5456
|
+
label: recommended.label,
|
|
5457
|
+
latencyBudgets: recommended.latencyBudgets,
|
|
5458
|
+
profileId: recommended.profileId,
|
|
5459
|
+
providerRoutes: recommended.providerRoutes,
|
|
5460
|
+
status: recommended.status
|
|
5461
|
+
} : undefined,
|
|
5462
|
+
status: issues.length > 0 ? "warn" : shouldSwitch ? "switch" : "stay"
|
|
5463
|
+
};
|
|
5464
|
+
};
|
|
5465
|
+
var applyVoiceProfileSwitchGuard = async (options) => {
|
|
5466
|
+
const mode = options.mode ?? "recommend";
|
|
5467
|
+
const minConfidence = options.minConfidence ?? 0.75;
|
|
5468
|
+
const recommendation = recommendVoiceProfileSwitch(options);
|
|
5469
|
+
const confidence = estimateSwitchConfidence(recommendation);
|
|
5470
|
+
const previousProfileId = recommendation.currentProfile?.profileId;
|
|
5471
|
+
const recommendedProfileId = recommendation.recommendedProfile?.profileId;
|
|
5472
|
+
const canSwitch = recommendation.status === "switch" && recommendation.ok && Boolean(recommendedProfileId) && confidence >= minConfidence;
|
|
5473
|
+
const action = recommendation.status === "stay" ? "stay" : canSwitch ? mode === "auto" ? "switch" : "recommend" : "blocked";
|
|
5474
|
+
const selectedProfileId = action === "switch" ? recommendedProfileId : previousProfileId ?? recommendedProfileId;
|
|
5475
|
+
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.";
|
|
5476
|
+
const decision = {
|
|
5477
|
+
action,
|
|
5478
|
+
autoApplied: action === "switch",
|
|
5479
|
+
confidence,
|
|
5480
|
+
minConfidence,
|
|
5481
|
+
mode,
|
|
5482
|
+
previousProfileId,
|
|
5483
|
+
reason,
|
|
5484
|
+
recommendation,
|
|
5485
|
+
recommendedProfileId,
|
|
5486
|
+
selectedProfileId
|
|
5487
|
+
};
|
|
5488
|
+
if (options.audit) {
|
|
5489
|
+
const auditEvent = await options.audit.append(createVoiceAuditEvent({
|
|
5490
|
+
action: `profile.switch.${action}`,
|
|
5491
|
+
actor: options.actor ?? {
|
|
5492
|
+
id: "absolutejs-voice-profile-switch-guard",
|
|
5493
|
+
kind: "system",
|
|
5494
|
+
name: "AbsoluteJS Voice Profile Switch Guard"
|
|
5495
|
+
},
|
|
5496
|
+
metadata: options.metadata,
|
|
5497
|
+
outcome: action === "blocked" ? "skipped" : "success",
|
|
5498
|
+
payload: {
|
|
5499
|
+
autoApplied: decision.autoApplied,
|
|
5500
|
+
confidence,
|
|
5501
|
+
minConfidence,
|
|
5502
|
+
mode,
|
|
5503
|
+
previousProfileId,
|
|
5504
|
+
reasons: recommendation.reasons,
|
|
5505
|
+
recommendedProfileId,
|
|
5506
|
+
selectedProfileId,
|
|
5507
|
+
status: recommendation.status
|
|
5508
|
+
},
|
|
5509
|
+
resource: {
|
|
5510
|
+
id: selectedProfileId,
|
|
5511
|
+
type: "voice-profile"
|
|
5512
|
+
},
|
|
5513
|
+
sessionId: options.sessionId,
|
|
5514
|
+
traceId: options.traceId,
|
|
5515
|
+
type: "profile.switch"
|
|
5516
|
+
}));
|
|
5517
|
+
decision.auditEvent = auditEvent;
|
|
5518
|
+
}
|
|
5519
|
+
return decision;
|
|
5520
|
+
};
|
|
5521
|
+
|
|
5203
5522
|
// src/plugin.ts
|
|
5204
5523
|
var resolveQueryScenario = (query) => {
|
|
5205
5524
|
if (typeof query?.scenarioId === "string" && query.scenarioId.trim()) {
|
|
@@ -5322,6 +5641,7 @@ var resolveSessionId = (runtime, ws) => {
|
|
|
5322
5641
|
runtime.socketSessions.set(ws, resolved);
|
|
5323
5642
|
return resolved;
|
|
5324
5643
|
};
|
|
5644
|
+
var resolveMaybeFunction = async (value, input) => typeof value === "function" ? await value(input) : value;
|
|
5325
5645
|
var toAudioChunk = (raw) => {
|
|
5326
5646
|
if (raw instanceof ArrayBuffer) {
|
|
5327
5647
|
return raw;
|
|
@@ -5395,6 +5715,69 @@ var resolveLexicon = async (config, input) => {
|
|
|
5395
5715
|
}
|
|
5396
5716
|
return normalizeLexicon(config.lexicon);
|
|
5397
5717
|
};
|
|
5718
|
+
var resolveProfileSwitchGuard = async (config, runtime, input) => {
|
|
5719
|
+
const guard = config.profileSwitchGuard;
|
|
5720
|
+
if (!guard || runtime.profileSwitchGuardedSessions.has(input.sessionId)) {
|
|
5721
|
+
return;
|
|
5722
|
+
}
|
|
5723
|
+
runtime.profileSwitchGuardedSessions.add(input.sessionId);
|
|
5724
|
+
const resolverInput = input;
|
|
5725
|
+
const defaults = await resolveMaybeFunction(guard.defaults, resolverInput);
|
|
5726
|
+
if (!defaults) {
|
|
5727
|
+
throw new Error("voice profileSwitchGuard requires measured profile defaults.");
|
|
5728
|
+
}
|
|
5729
|
+
const observed = await resolveMaybeFunction(guard.observed, resolverInput);
|
|
5730
|
+
const currentProfileId = await resolveMaybeFunction(guard.currentProfileId, resolverInput);
|
|
5731
|
+
const metadata = await resolveMaybeFunction(guard.metadata, resolverInput);
|
|
5732
|
+
const minConfidence = await resolveMaybeFunction(guard.minConfidence, resolverInput);
|
|
5733
|
+
const mode = await resolveMaybeFunction(guard.mode, resolverInput);
|
|
5734
|
+
const decision = await applyVoiceProfileSwitchGuard({
|
|
5735
|
+
actor: guard.actor,
|
|
5736
|
+
audit: guard.audit,
|
|
5737
|
+
defaultProfileId: guard.defaultProfileId,
|
|
5738
|
+
defaults,
|
|
5739
|
+
metadata,
|
|
5740
|
+
minConfidence,
|
|
5741
|
+
mode,
|
|
5742
|
+
observed: {
|
|
5743
|
+
...observed,
|
|
5744
|
+
currentProfileId: observed?.currentProfileId ?? currentProfileId
|
|
5745
|
+
},
|
|
5746
|
+
sessionId: input.sessionId
|
|
5747
|
+
});
|
|
5748
|
+
await guard.onDecision?.({
|
|
5749
|
+
context: input.context,
|
|
5750
|
+
decision,
|
|
5751
|
+
scenarioId: input.scenarioId,
|
|
5752
|
+
sessionId: input.sessionId
|
|
5753
|
+
});
|
|
5754
|
+
const trace = guard.trace === false ? undefined : guard.trace ?? config.trace;
|
|
5755
|
+
if (trace) {
|
|
5756
|
+
await trace.append({
|
|
5757
|
+
at: Date.now(),
|
|
5758
|
+
metadata: {
|
|
5759
|
+
...metadata,
|
|
5760
|
+
source: "profile-switch-guard"
|
|
5761
|
+
},
|
|
5762
|
+
payload: {
|
|
5763
|
+
action: decision.action,
|
|
5764
|
+
autoApplied: decision.autoApplied,
|
|
5765
|
+
confidence: decision.confidence,
|
|
5766
|
+
minConfidence: decision.minConfidence,
|
|
5767
|
+
mode: decision.mode,
|
|
5768
|
+
previousProfileId: decision.previousProfileId,
|
|
5769
|
+
reason: decision.reason,
|
|
5770
|
+
recommendedProfileId: decision.recommendedProfileId,
|
|
5771
|
+
selectedProfileId: decision.selectedProfileId,
|
|
5772
|
+
status: decision.recommendation.status
|
|
5773
|
+
},
|
|
5774
|
+
scenarioId: input.scenarioId,
|
|
5775
|
+
sessionId: input.sessionId,
|
|
5776
|
+
type: "provider.decision"
|
|
5777
|
+
});
|
|
5778
|
+
}
|
|
5779
|
+
return decision;
|
|
5780
|
+
};
|
|
5398
5781
|
var voice = (config) => {
|
|
5399
5782
|
if (!config.stt && !config.realtime) {
|
|
5400
5783
|
throw new Error("voice requires either an stt or realtime adapter.");
|
|
@@ -5402,6 +5785,7 @@ var voice = (config) => {
|
|
|
5402
5785
|
const runtime = {
|
|
5403
5786
|
activeSessions: new Map,
|
|
5404
5787
|
logger: resolveLogger(config.logger),
|
|
5788
|
+
profileSwitchGuardedSessions: new Set,
|
|
5405
5789
|
socketSessions: new WeakMap
|
|
5406
5790
|
};
|
|
5407
5791
|
const onTurn = normalizeOnTurn(config.onTurn);
|
|
@@ -5413,6 +5797,11 @@ var voice = (config) => {
|
|
|
5413
5797
|
const htmxTargets = resolveVoiceHTMXTargets(htmxOptions?.targets);
|
|
5414
5798
|
const createManagedSession = async (ws, sessionId, scenarioId) => {
|
|
5415
5799
|
const context = ws.data;
|
|
5800
|
+
const profileSwitchDecision = await resolveProfileSwitchGuard(config, runtime, {
|
|
5801
|
+
context,
|
|
5802
|
+
scenarioId,
|
|
5803
|
+
sessionId
|
|
5804
|
+
});
|
|
5416
5805
|
const phraseHints = await resolvePhraseHints(config, {
|
|
5417
5806
|
context,
|
|
5418
5807
|
scenarioId,
|
|
@@ -5470,6 +5859,9 @@ var voice = (config) => {
|
|
|
5470
5859
|
onTurn,
|
|
5471
5860
|
onVoicemail: config.onVoicemail
|
|
5472
5861
|
},
|
|
5862
|
+
sessionMetadata: profileSwitchDecision && config.profileSwitchGuard?.sessionMetadataKey !== false ? {
|
|
5863
|
+
[config.profileSwitchGuard?.sessionMetadataKey ?? "profileSwitchGuard"]: profileSwitchDecision
|
|
5864
|
+
} : undefined,
|
|
5473
5865
|
scenarioId,
|
|
5474
5866
|
socket: createSocketAdapter(ws),
|
|
5475
5867
|
store: config.session,
|
|
@@ -7198,170 +7590,6 @@ var assertVoiceCampaignDialerProofEvidence = (report, input = {}) => {
|
|
|
7198
7590
|
}
|
|
7199
7591
|
return assertion;
|
|
7200
7592
|
};
|
|
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)
|
|
7265
|
-
};
|
|
7266
|
-
};
|
|
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
|
-
|
|
7365
7593
|
// src/agent.ts
|
|
7366
7594
|
var normalizeText3 = (value) => typeof value === "string" ? value.trim() : "";
|
|
7367
7595
|
var toErrorMessage3 = (error) => error instanceof Error ? error.message : String(error);
|
|
@@ -15694,72 +15922,6 @@ var formatVoiceProofTrendAge = (ageMs) => {
|
|
|
15694
15922
|
const days = Math.floor(hours / 24);
|
|
15695
15923
|
return `${days}d ${hours % 24}h`;
|
|
15696
15924
|
};
|
|
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 recommendVoiceProfileSwitch = (options) => {
|
|
15711
|
-
const defaults = readDefaults(options.defaults);
|
|
15712
|
-
const observed = options.observed ?? {};
|
|
15713
|
-
const currentProfileId = observed.currentProfileId ?? options.defaultProfileId ?? defaults.profiles[0]?.profileId;
|
|
15714
|
-
const currentProfile = defaults.profiles.find((profile) => profile.profileId === currentProfileId);
|
|
15715
|
-
const candidates = defaults.profiles.filter((profile) => profile.status !== "fail");
|
|
15716
|
-
const recommended = candidates.slice().sort((left, right) => scoreProfile(left, observed) - scoreProfile(right, observed))[0];
|
|
15717
|
-
const issues = [
|
|
15718
|
-
...defaults.profiles.length === 0 ? ["No measured profile defaults are available."] : [],
|
|
15719
|
-
...!currentProfile && currentProfileId ? [`Current profile ${currentProfileId} is not present in measured defaults.`] : [],
|
|
15720
|
-
...!recommended ? ["No non-failing measured profile can be recommended."] : []
|
|
15721
|
-
];
|
|
15722
|
-
const currentOverBudget = currentProfile ? [
|
|
15723
|
-
exceeds(observed.liveP95Ms, currentProfile.latencyBudgets.maxLiveP95Ms) ? "live p95 exceeds this profile budget" : undefined,
|
|
15724
|
-
exceeds(observed.providerP95Ms, currentProfile.latencyBudgets.maxProviderP95Ms) ? "provider p95 exceeds this profile budget" : undefined,
|
|
15725
|
-
exceeds(observed.turnP95Ms, currentProfile.latencyBudgets.maxTurnP95Ms) ? "turn p95 exceeds this profile budget" : undefined
|
|
15726
|
-
].filter((reason) => Boolean(reason)) : [];
|
|
15727
|
-
const minImprovementMs = options.minImprovementMs ?? 250;
|
|
15728
|
-
const currentScore = currentProfile ? scoreProfile(currentProfile, observed) : Number.POSITIVE_INFINITY;
|
|
15729
|
-
const recommendedScore = recommended ? scoreProfile(recommended, observed) : Number.POSITIVE_INFINITY;
|
|
15730
|
-
const shouldSwitch = Boolean(recommended) && recommended?.profileId !== currentProfile?.profileId && (!currentProfile || currentProfile.status !== "pass" || currentOverBudget.length > 0 || currentScore - recommendedScore >= minImprovementMs);
|
|
15731
|
-
const reasons = [
|
|
15732
|
-
...currentOverBudget,
|
|
15733
|
-
...currentProfile?.status && currentProfile.status !== "pass" ? [`current profile is ${currentProfile.status}`] : [],
|
|
15734
|
-
...observed.fallbackUsed ? ["current session used provider fallback"] : [],
|
|
15735
|
-
...(observed.turnWarnings ?? 0) > 0 ? [`${observed.turnWarnings} turn quality warning(s) observed`] : [],
|
|
15736
|
-
...shouldSwitch && recommended ? [
|
|
15737
|
-
`${recommended.label ?? recommended.profileId} has the strongest measured fit for these signals`
|
|
15738
|
-
] : []
|
|
15739
|
-
];
|
|
15740
|
-
return {
|
|
15741
|
-
currentProfile: currentProfile ? {
|
|
15742
|
-
label: currentProfile.label,
|
|
15743
|
-
profileId: currentProfile.profileId,
|
|
15744
|
-
status: currentProfile.status
|
|
15745
|
-
} : undefined,
|
|
15746
|
-
generatedAt: new Date().toISOString(),
|
|
15747
|
-
issues,
|
|
15748
|
-
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.",
|
|
15749
|
-
ok: issues.length === 0,
|
|
15750
|
-
observed,
|
|
15751
|
-
reasons: reasons.length > 0 ? reasons : ["current profile matches measured defaults and observed budgets"],
|
|
15752
|
-
recommendedProfile: recommended ? {
|
|
15753
|
-
evidence: recommended.evidence,
|
|
15754
|
-
label: recommended.label,
|
|
15755
|
-
latencyBudgets: recommended.latencyBudgets,
|
|
15756
|
-
profileId: recommended.profileId,
|
|
15757
|
-
providerRoutes: recommended.providerRoutes,
|
|
15758
|
-
status: recommended.status
|
|
15759
|
-
} : undefined,
|
|
15760
|
-
status: issues.length > 0 ? "warn" : shouldSwitch ? "switch" : "stay"
|
|
15761
|
-
};
|
|
15762
|
-
};
|
|
15763
15925
|
// src/providerDecisionTraces.ts
|
|
15764
15926
|
import { Elysia as Elysia23 } from "elysia";
|
|
15765
15927
|
|
|
@@ -37846,6 +38008,7 @@ export {
|
|
|
37846
38008
|
assertVoiceAgentSquadContractEvidence,
|
|
37847
38009
|
assertVoiceAgentSquadContract,
|
|
37848
38010
|
applyVoiceTelephonyOutcome,
|
|
38011
|
+
applyVoiceProfileSwitchGuard,
|
|
37849
38012
|
applyVoiceOpsTaskPolicy,
|
|
37850
38013
|
applyVoiceOpsTaskAssignmentRule,
|
|
37851
38014
|
applyVoiceHandoffDeliveryResult,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { VoiceRealCallProfileDefault, VoiceRealCallProfileDefaultsReport, VoiceRealCallProfileHistoryReport } from './proofTrends';
|
|
2
|
+
import { type StoredVoiceAuditEvent, type VoiceAuditActor, type VoiceAuditEventStore } from './audit';
|
|
2
3
|
export type VoiceProfileSwitchObservedSignals = {
|
|
3
4
|
currentProfileId?: string;
|
|
4
5
|
fallbackUsed?: boolean;
|
|
@@ -35,4 +36,29 @@ export type VoiceProfileSwitchRecommendationOptions = {
|
|
|
35
36
|
minImprovementMs?: number;
|
|
36
37
|
observed?: VoiceProfileSwitchObservedSignals;
|
|
37
38
|
};
|
|
39
|
+
export type VoiceProfileSwitchGuardMode = 'auto' | 'recommend';
|
|
40
|
+
export type VoiceProfileSwitchGuardAction = 'blocked' | 'recommend' | 'stay' | 'switch';
|
|
41
|
+
export type VoiceProfileSwitchGuardDecision = {
|
|
42
|
+
action: VoiceProfileSwitchGuardAction;
|
|
43
|
+
auditEvent?: StoredVoiceAuditEvent;
|
|
44
|
+
autoApplied: boolean;
|
|
45
|
+
confidence: number;
|
|
46
|
+
minConfidence: number;
|
|
47
|
+
mode: VoiceProfileSwitchGuardMode;
|
|
48
|
+
previousProfileId?: string;
|
|
49
|
+
reason: string;
|
|
50
|
+
recommendation: VoiceProfileSwitchRecommendation;
|
|
51
|
+
recommendedProfileId?: string;
|
|
52
|
+
selectedProfileId?: string;
|
|
53
|
+
};
|
|
54
|
+
export type VoiceProfileSwitchGuardOptions = VoiceProfileSwitchRecommendationOptions & {
|
|
55
|
+
actor?: VoiceAuditActor;
|
|
56
|
+
audit?: VoiceAuditEventStore;
|
|
57
|
+
metadata?: Record<string, unknown>;
|
|
58
|
+
minConfidence?: number;
|
|
59
|
+
mode?: VoiceProfileSwitchGuardMode;
|
|
60
|
+
sessionId?: string;
|
|
61
|
+
traceId?: string;
|
|
62
|
+
};
|
|
38
63
|
export declare const recommendVoiceProfileSwitch: (options: VoiceProfileSwitchRecommendationOptions) => VoiceProfileSwitchRecommendation;
|
|
64
|
+
export declare const applyVoiceProfileSwitchGuard: (options: VoiceProfileSwitchGuardOptions) => Promise<VoiceProfileSwitchGuardDecision>;
|
package/dist/testing/index.js
CHANGED
|
@@ -7563,6 +7563,12 @@ var createVoiceSession = (options) => {
|
|
|
7563
7563
|
if (options.scenarioId && session.scenarioId !== options.scenarioId) {
|
|
7564
7564
|
session.scenarioId = options.scenarioId;
|
|
7565
7565
|
}
|
|
7566
|
+
if (options.sessionMetadata) {
|
|
7567
|
+
session.metadata = {
|
|
7568
|
+
...session.metadata && typeof session.metadata === "object" ? session.metadata : {},
|
|
7569
|
+
...options.sessionMetadata
|
|
7570
|
+
};
|
|
7571
|
+
}
|
|
7566
7572
|
ensureCommittedTurnGuard(session);
|
|
7567
7573
|
let shouldFireOnSession = !existingSession;
|
|
7568
7574
|
if (existingSession?.scenarioId && options.scenarioId && existingSession.scenarioId !== options.scenarioId) {
|
package/dist/types.d.ts
CHANGED
|
@@ -5,6 +5,9 @@ import type { VoiceIntegrationSink } from './opsSinks';
|
|
|
5
5
|
import type { StoredVoiceCallReviewArtifact, VoiceCallReviewArtifact, VoiceCallReviewStore } from './testing/review';
|
|
6
6
|
import type { VoiceTraceEventStore } from './trace';
|
|
7
7
|
import type { VoiceLiveOpsControlState } from './liveOps';
|
|
8
|
+
import type { VoiceAuditActor, VoiceAuditEventStore } from './audit';
|
|
9
|
+
import type { VoiceProfileSwitchGuardDecision, VoiceProfileSwitchGuardMode, VoiceProfileSwitchObservedSignals } from './profileSwitchRecommendation';
|
|
10
|
+
import type { VoiceRealCallProfileDefaultsReport, VoiceRealCallProfileHistoryReport } from './proofTrends';
|
|
8
11
|
export type AudioFormat = {
|
|
9
12
|
container: 'raw';
|
|
10
13
|
encoding: 'alaw' | 'mulaw' | 'pcm_s16le';
|
|
@@ -614,6 +617,30 @@ export type VoiceRuntimeOpsConfig<TContext = unknown, TSession extends VoiceSess
|
|
|
614
617
|
export type VoiceLiveOpsRuntimeConfig = {
|
|
615
618
|
getControl: (sessionId: string) => Promise<VoiceLiveOpsControlState | null | undefined> | VoiceLiveOpsControlState | null | undefined;
|
|
616
619
|
};
|
|
620
|
+
export type VoiceProfileSwitchGuardResolverInput<TContext = unknown> = {
|
|
621
|
+
context: TContext;
|
|
622
|
+
scenarioId?: string;
|
|
623
|
+
sessionId: string;
|
|
624
|
+
};
|
|
625
|
+
export type VoicePluginProfileSwitchGuardConfig<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
626
|
+
actor?: VoiceAuditActor;
|
|
627
|
+
audit?: VoiceAuditEventStore;
|
|
628
|
+
currentProfileId?: string | ((input: VoiceProfileSwitchGuardResolverInput<TContext>) => Promise<string | undefined> | string | undefined);
|
|
629
|
+
defaultProfileId?: string;
|
|
630
|
+
defaults: VoiceRealCallProfileDefaultsReport | VoiceRealCallProfileHistoryReport | ((input: VoiceProfileSwitchGuardResolverInput<TContext>) => Promise<VoiceRealCallProfileDefaultsReport | VoiceRealCallProfileHistoryReport> | VoiceRealCallProfileDefaultsReport | VoiceRealCallProfileHistoryReport);
|
|
631
|
+
metadata?: Record<string, unknown> | ((input: VoiceProfileSwitchGuardResolverInput<TContext>) => Promise<Record<string, unknown> | undefined> | Record<string, unknown> | undefined);
|
|
632
|
+
minConfidence?: number | ((input: VoiceProfileSwitchGuardResolverInput<TContext>) => Promise<number | undefined> | number | undefined);
|
|
633
|
+
mode?: VoiceProfileSwitchGuardMode | ((input: VoiceProfileSwitchGuardResolverInput<TContext>) => Promise<VoiceProfileSwitchGuardMode | undefined> | VoiceProfileSwitchGuardMode | undefined);
|
|
634
|
+
observed?: VoiceProfileSwitchObservedSignals | ((input: VoiceProfileSwitchGuardResolverInput<TContext>) => Promise<VoiceProfileSwitchObservedSignals | undefined> | VoiceProfileSwitchObservedSignals | undefined);
|
|
635
|
+
onDecision?: (input: {
|
|
636
|
+
context: TContext;
|
|
637
|
+
decision: VoiceProfileSwitchGuardDecision;
|
|
638
|
+
scenarioId?: string;
|
|
639
|
+
sessionId: string;
|
|
640
|
+
}) => Promise<void> | void;
|
|
641
|
+
sessionMetadataKey?: string | false;
|
|
642
|
+
trace?: false | VoiceTraceEventStore;
|
|
643
|
+
};
|
|
617
644
|
export type VoiceNormalizedRouteConfig<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = Omit<VoiceRouteConfig<TContext, TSession, TResult>, 'onTurn'> & {
|
|
618
645
|
onTurn: VoiceOnTurnObjectHandler<TContext, TSession, TResult>;
|
|
619
646
|
};
|
|
@@ -649,6 +676,7 @@ export type VoicePluginConfig<TContext = unknown, TSession extends VoiceSessionR
|
|
|
649
676
|
handoff?: VoiceHandoffConfig<TContext, TSession, TResult>;
|
|
650
677
|
ops?: VoiceRuntimeOpsConfig<TContext, TSession, TResult>;
|
|
651
678
|
liveOps?: VoiceLiveOpsRuntimeConfig;
|
|
679
|
+
profileSwitchGuard?: VoicePluginProfileSwitchGuardConfig<TContext, TSession, TResult>;
|
|
652
680
|
trace?: VoiceTraceEventStore;
|
|
653
681
|
} & VoiceRouteConfig<TContext, TSession, TResult>;
|
|
654
682
|
export type CreateVoiceSessionOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
@@ -667,6 +695,7 @@ export type CreateVoiceSessionOptions<TContext = unknown, TSession extends Voice
|
|
|
667
695
|
trace?: VoiceTraceEventStore;
|
|
668
696
|
reconnect: Required<VoiceReconnectConfig>;
|
|
669
697
|
phraseHints?: VoicePhraseHint[];
|
|
698
|
+
sessionMetadata?: Record<string, unknown>;
|
|
670
699
|
scenarioId?: string;
|
|
671
700
|
sttLifecycle: VoiceSTTLifecycle;
|
|
672
701
|
turnDetection: VoiceResolvedTurnDetectionConfig;
|
|
@@ -146,6 +146,7 @@ export declare const useVoiceReadinessFailures: (path?: string, options?: VoiceR
|
|
|
146
146
|
readonly present: {
|
|
147
147
|
readonly "operator.action": number;
|
|
148
148
|
readonly handoff: number;
|
|
149
|
+
readonly "profile.switch": number;
|
|
149
150
|
readonly "provider.call": number;
|
|
150
151
|
readonly "retention.policy": number;
|
|
151
152
|
readonly "tool.call": number;
|
|
@@ -578,6 +579,7 @@ export declare const useVoiceReadinessFailures: (path?: string, options?: VoiceR
|
|
|
578
579
|
readonly present: {
|
|
579
580
|
readonly "operator.action": number;
|
|
580
581
|
readonly handoff: number;
|
|
582
|
+
readonly "profile.switch": number;
|
|
581
583
|
readonly "provider.call": number;
|
|
582
584
|
readonly "retention.policy": number;
|
|
583
585
|
readonly "tool.call": number;
|