@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/angular/index.js +16 -0
- package/dist/angular/voice-stream.service.d.ts +1 -0
- package/dist/client/actions.d.ts +11 -0
- package/dist/client/htmxBootstrap.js +13 -0
- package/dist/client/index.js +13 -0
- package/dist/index.js +404 -322
- package/dist/react/index.js +14 -0
- package/dist/react/useVoiceController.d.ts +1 -0
- package/dist/react/useVoiceStream.d.ts +1 -0
- package/dist/svelte/index.js +13 -0
- package/dist/testing/index.js +21 -0
- package/dist/types.d.ts +36 -0
- package/dist/vue/index.js +16 -0
- package/dist/vue/useVoiceStream.d.ts +1 -0
- package/package.json +1 -1
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
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
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
|
|