@absolutejs/voice 0.0.22-beta.384 → 0.0.22-beta.385
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/client/index.js +130 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +131 -0
- package/dist/proofTrends.d.ts +42 -1
- package/dist/react/index.js +130 -0
- package/dist/vue/index.js +130 -0
- package/package.json +1 -1
package/dist/client/index.js
CHANGED
|
@@ -4686,6 +4686,136 @@ var appendRealCallRecoveryActionQuery = (href, query) => {
|
|
|
4686
4686
|
const search = new URLSearchParams(entries).toString();
|
|
4687
4687
|
return `${base}${separator}${search}${hash ? `#${hash}` : ""}`;
|
|
4688
4688
|
};
|
|
4689
|
+
var sleepVoiceRealCallProfileRecoveryLoop = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
4690
|
+
var describeVoiceRealCallProfileRecoveryLoopAction = (action) => [
|
|
4691
|
+
action.label ?? action.id ?? "recovery action",
|
|
4692
|
+
action.profileId ? `profile=${action.profileId}` : undefined,
|
|
4693
|
+
action.href
|
|
4694
|
+
].filter(Boolean).join(" ");
|
|
4695
|
+
var defaultVoiceRealCallProfileRecoveryLoopActionFilter = (action, readinessCheckLabel) => action.method?.toUpperCase() === "POST" && action.sourceCheckLabel === readinessCheckLabel && typeof action.href === "string" && action.href.length > 0;
|
|
4696
|
+
var uniqueVoiceRealCallProfileRecoveryLoopActions = (actions) => {
|
|
4697
|
+
const seen = new Set;
|
|
4698
|
+
return actions.filter((action) => {
|
|
4699
|
+
const key = `${action.method?.toUpperCase() ?? "GET"} ${action.href ?? ""}`;
|
|
4700
|
+
if (seen.has(key)) {
|
|
4701
|
+
return false;
|
|
4702
|
+
}
|
|
4703
|
+
seen.add(key);
|
|
4704
|
+
return true;
|
|
4705
|
+
});
|
|
4706
|
+
};
|
|
4707
|
+
var runVoiceRealCallProfileRecoveryLoop = async (options) => {
|
|
4708
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
4709
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
|
|
4710
|
+
const jobPollMs = options.jobPollMs ?? 1200;
|
|
4711
|
+
const jobTimeoutMs = options.jobTimeoutMs ?? 600000;
|
|
4712
|
+
const readinessCheckLabel = options.readinessCheckLabel ?? "Real-call profile history";
|
|
4713
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
4714
|
+
const recoveryActionsHref = options.recoveryActionsHref ?? "/api/production-readiness/recovery-actions";
|
|
4715
|
+
const readinessHref = options.readinessHref ?? "/api/production-readiness";
|
|
4716
|
+
const refreshHref = options.refreshHref === undefined ? "/api/voice/real-call-profile-history/refresh" : options.refreshHref;
|
|
4717
|
+
const jobHref = options.jobHref ?? "/api/voice/real-call-profile-history/actions";
|
|
4718
|
+
const toAbsoluteUrl = (href) => new URL(href, baseUrl).toString();
|
|
4719
|
+
const parseJson = async (response) => {
|
|
4720
|
+
const text = await response.text();
|
|
4721
|
+
try {
|
|
4722
|
+
return JSON.parse(text);
|
|
4723
|
+
} catch (error) {
|
|
4724
|
+
throw new Error(`Expected JSON from ${response.url}, got: ${text.slice(0, 300)}`, { cause: error });
|
|
4725
|
+
}
|
|
4726
|
+
};
|
|
4727
|
+
const fetchJson = async (href, init) => {
|
|
4728
|
+
const response = await fetchImpl(toAbsoluteUrl(href), {
|
|
4729
|
+
headers: { accept: "application/json", ...init?.headers },
|
|
4730
|
+
...init,
|
|
4731
|
+
signal: init?.signal ?? AbortSignal.timeout(requestTimeoutMs)
|
|
4732
|
+
});
|
|
4733
|
+
if (!response.ok) {
|
|
4734
|
+
throw new Error(`${href} returned HTTP ${String(response.status)}.`);
|
|
4735
|
+
}
|
|
4736
|
+
return parseJson(response);
|
|
4737
|
+
};
|
|
4738
|
+
const resolveJobHref = (jobId) => typeof jobHref === "function" ? jobHref(jobId) : `${jobHref.replace(/\/$/, "")}/${jobId}`;
|
|
4739
|
+
const getGate = async (fresh = false) => {
|
|
4740
|
+
const href = fresh ? `${readinessHref}${readinessHref.includes("?") ? "&" : "?"}voiceRecoveryLoopFresh=${String(Date.now())}` : readinessHref;
|
|
4741
|
+
const readiness = await fetchJson(href);
|
|
4742
|
+
return readiness.checks?.find((check) => check.label === readinessCheckLabel) ?? null;
|
|
4743
|
+
};
|
|
4744
|
+
const actionsResponse = await fetchJson(recoveryActionsHref);
|
|
4745
|
+
const actionFilter = options.actionFilter ?? ((action) => defaultVoiceRealCallProfileRecoveryLoopActionFilter(action, readinessCheckLabel));
|
|
4746
|
+
const actions = uniqueVoiceRealCallProfileRecoveryLoopActions((actionsResponse.actions ?? []).filter(actionFilter));
|
|
4747
|
+
if (actions.length === 0) {
|
|
4748
|
+
const realCallProfileGate2 = await getGate();
|
|
4749
|
+
return {
|
|
4750
|
+
actionCount: 0,
|
|
4751
|
+
actions,
|
|
4752
|
+
jobs: [],
|
|
4753
|
+
ok: realCallProfileGate2?.status === "pass",
|
|
4754
|
+
realCallProfileGate: realCallProfileGate2,
|
|
4755
|
+
startFailures: []
|
|
4756
|
+
};
|
|
4757
|
+
}
|
|
4758
|
+
options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel.`);
|
|
4759
|
+
for (const action of actions) {
|
|
4760
|
+
options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
|
|
4761
|
+
}
|
|
4762
|
+
const starts = await Promise.allSettled(actions.map(async (action) => {
|
|
4763
|
+
if (!action.href) {
|
|
4764
|
+
throw new Error("Recovery action is missing href.");
|
|
4765
|
+
}
|
|
4766
|
+
const body = await fetchJson(action.href, { method: "POST" });
|
|
4767
|
+
return { action, ...body };
|
|
4768
|
+
}));
|
|
4769
|
+
const startedJobs = starts.flatMap((result) => {
|
|
4770
|
+
if (result.status === "rejected") {
|
|
4771
|
+
return [];
|
|
4772
|
+
}
|
|
4773
|
+
return result.value.jobId ? [result.value] : [];
|
|
4774
|
+
});
|
|
4775
|
+
const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
|
|
4776
|
+
{
|
|
4777
|
+
action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
|
|
4778
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
4779
|
+
}
|
|
4780
|
+
] : []);
|
|
4781
|
+
const pollJob = async (jobId) => {
|
|
4782
|
+
const deadline = Date.now() + jobTimeoutMs;
|
|
4783
|
+
while (Date.now() < deadline) {
|
|
4784
|
+
const body = await fetchJson(resolveJobHref(jobId));
|
|
4785
|
+
const job = body.job;
|
|
4786
|
+
if (!job) {
|
|
4787
|
+
throw new Error(`Recovery job ${jobId} was not found.`);
|
|
4788
|
+
}
|
|
4789
|
+
if (job.status === "pass" || job.status === "fail") {
|
|
4790
|
+
return job;
|
|
4791
|
+
}
|
|
4792
|
+
await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
|
|
4793
|
+
}
|
|
4794
|
+
throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
|
|
4795
|
+
};
|
|
4796
|
+
options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
|
|
4797
|
+
const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
|
|
4798
|
+
const jobs = jobResults.map((result, index) => ({
|
|
4799
|
+
action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
|
|
4800
|
+
jobId: startedJobs[index]?.jobId,
|
|
4801
|
+
result: result.status === "fulfilled" ? result.value : {
|
|
4802
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason),
|
|
4803
|
+
status: "fail"
|
|
4804
|
+
}
|
|
4805
|
+
}));
|
|
4806
|
+
if (refreshHref !== false) {
|
|
4807
|
+
await fetchJson(refreshHref, { method: "POST" });
|
|
4808
|
+
}
|
|
4809
|
+
const realCallProfileGate = await getGate(true);
|
|
4810
|
+
return {
|
|
4811
|
+
actionCount: actions.length,
|
|
4812
|
+
actions,
|
|
4813
|
+
jobs,
|
|
4814
|
+
ok: startFailures.length === 0 && jobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
|
|
4815
|
+
realCallProfileGate,
|
|
4816
|
+
startFailures
|
|
4817
|
+
};
|
|
4818
|
+
};
|
|
4689
4819
|
var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
|
|
4690
4820
|
const actions = [
|
|
4691
4821
|
{
|
package/dist/index.d.ts
CHANGED
|
@@ -30,12 +30,12 @@ export { assertVoicePlatformCoverage, buildVoicePlatformCoverageSummary, createV
|
|
|
30
30
|
export { assertVoiceCompetitiveCoverage, buildVoiceCompetitiveCoverageReport, createVoiceCompetitiveCoverageRoutes, evaluateVoiceCompetitiveCoverage, renderVoiceCompetitiveCoverageHTML, renderVoiceCompetitiveCoverageMarkdown } from './competitiveCoverage';
|
|
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
|
-
export { assertVoiceProofTrendEvidence, buildEmptyVoiceProofTrendReport, buildVoiceProofTrendProfileSummaries, buildVoiceProofTrendRecommendationReport, buildVoiceProofTrendReportFromRealCallProfiles, buildVoiceProofTrendReport, buildVoiceRealCallProfileEvidenceFromTraceEvents, buildVoiceRealCallProfileDefaults, buildVoiceRealCallProfileHistoryReport, buildVoiceRealCallProfileReadinessCheck, buildVoiceRealCallProfileRecoveryJobHistoryCheck, buildVoiceRealCallProfileRecoveryActions, createVoiceInMemoryRealCallProfileRecoveryJobStore, createVoiceRealCallProfileTraceCollector, createVoiceSQLiteRealCallProfileRecoveryJobStore, createVoiceProofTrendRecommendationRoutes, createVoiceProofTrendRoutes, createVoiceRealCallProfileHistoryRoutes, createVoiceRealCallProfileRecoveryActionRoutes, DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS, DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS, evaluateVoiceProofTrendEvidence, formatVoiceProofTrendAge, loadVoiceRealCallProfileEvidenceFromTraceStore, normalizeVoiceProofTrendReport, readVoiceProofTrendReportFile, renderVoiceProofTrendRecommendationHTML, renderVoiceProofTrendRecommendationMarkdown, renderVoiceRealCallProfileHistoryHTML, renderVoiceRealCallProfileHistoryMarkdown, resolveVoiceRealCallProfileProviderRoute } from './proofTrends';
|
|
33
|
+
export { assertVoiceProofTrendEvidence, buildEmptyVoiceProofTrendReport, buildVoiceProofTrendProfileSummaries, buildVoiceProofTrendRecommendationReport, buildVoiceProofTrendReportFromRealCallProfiles, buildVoiceProofTrendReport, buildVoiceRealCallProfileEvidenceFromTraceEvents, buildVoiceRealCallProfileDefaults, buildVoiceRealCallProfileHistoryReport, buildVoiceRealCallProfileReadinessCheck, buildVoiceRealCallProfileRecoveryJobHistoryCheck, buildVoiceRealCallProfileRecoveryActions, createVoiceInMemoryRealCallProfileRecoveryJobStore, createVoiceRealCallProfileTraceCollector, createVoiceSQLiteRealCallProfileRecoveryJobStore, createVoiceProofTrendRecommendationRoutes, createVoiceProofTrendRoutes, createVoiceRealCallProfileHistoryRoutes, createVoiceRealCallProfileRecoveryActionRoutes, DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS, DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS, evaluateVoiceProofTrendEvidence, formatVoiceProofTrendAge, loadVoiceRealCallProfileEvidenceFromTraceStore, normalizeVoiceProofTrendReport, readVoiceProofTrendReportFile, renderVoiceProofTrendRecommendationHTML, renderVoiceProofTrendRecommendationMarkdown, renderVoiceRealCallProfileHistoryHTML, renderVoiceRealCallProfileHistoryMarkdown, runVoiceRealCallProfileRecoveryLoop, resolveVoiceRealCallProfileProviderRoute } from './proofTrends';
|
|
34
34
|
export { applyVoiceProfileSwitchGuard, buildVoiceProfileSwitchReadinessReport, buildVoiceProfileSwitchLiveDecisionReport, createVoiceProfileSwitchLiveDecisionRoutes, createVoiceProfileSwitchPolicyProofRoutes, createVoiceProfileSwitchReadinessRoutes, recommendVoiceProfileSwitch, renderVoiceProfileSwitchLiveDecisionHTML, renderVoiceProfileSwitchPolicyProofHTML, renderVoiceProfileSwitchReadinessHTML, runVoiceProfileSwitchPolicyProof } from './profileSwitchRecommendation';
|
|
35
35
|
export type { VoiceProfileSwitchGuardAction, VoiceProfileSwitchGuardDecision, VoiceProfileSwitchGuardMode, VoiceProfileSwitchGuardOptions, VoiceProfileSwitchObservedSignals, VoiceProfileSwitchLiveDecisionEvidence, VoiceProfileSwitchLiveDecisionReport, VoiceProfileSwitchLiveDecisionReportOptions, VoiceProfileSwitchLiveDecisionRoutesOptions, VoiceProfileSwitchLiveDecisionSession, VoiceProfileSwitchPolicyProofCase, VoiceProfileSwitchPolicyProofCaseResult, VoiceProfileSwitchPolicyProofOptions, VoiceProfileSwitchPolicyProofReport, VoiceProfileSwitchPolicyProofRoutesOptions, VoiceProfileSwitchReadinessIssue, VoiceProfileSwitchReadinessOptions, VoiceProfileSwitchReadinessReport, VoiceProfileSwitchReadinessRoutesOptions, VoiceProfileSwitchReadinessStatus, 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
|
-
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, VoiceRealCallProfileReadinessCheckOptions, VoiceRealCallProfileRecoveryActionOptions, VoiceRealCallProfileRecoveryAction, VoiceRealCallProfileRecoveryActionHandler, VoiceRealCallProfileRecoveryActionHandlerInput, VoiceRealCallProfileRecoveryActionId, VoiceRealCallProfileRecoveryJobHistoryCheckOptions, VoiceRealCallProfileRecoveryActionResult, VoiceRealCallProfileRecoveryActionRoutesOptions, VoiceRealCallProfileRecoveryJob, VoiceRealCallProfileRecoveryJobCreateInput, VoiceRealCallProfileRecoveryJobListOptions, VoiceRealCallProfileRecoveryJobStatus, VoiceRealCallProfileRecoveryJobStore, VoiceRealCallProfileRecoveryJobUpdate, VoiceSQLiteRealCallProfileRecoveryJobStoreOptions, VoiceRealCallProfileTraceCollector, VoiceRealCallProfileTraceCollectorEvidenceOptions, VoiceRealCallProfileTraceCollectorOptions, VoiceRealCallProfileTraceEvidenceOptions, VoiceRealCallProfileTraceStoreEvidenceOptions } from './proofTrends';
|
|
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, VoiceRealCallProfileReadinessCheckOptions, VoiceRealCallProfileRecoveryActionOptions, VoiceRealCallProfileRecoveryAction, VoiceRealCallProfileRecoveryActionHandler, VoiceRealCallProfileRecoveryActionHandlerInput, VoiceRealCallProfileRecoveryActionId, VoiceRealCallProfileRecoveryJobHistoryCheckOptions, VoiceRealCallProfileRecoveryActionResult, VoiceRealCallProfileRecoveryActionRoutesOptions, VoiceRealCallProfileRecoveryJob, VoiceRealCallProfileRecoveryJobCreateInput, VoiceRealCallProfileRecoveryJobListOptions, VoiceRealCallProfileRecoveryJobStatus, VoiceRealCallProfileRecoveryJobStore, VoiceRealCallProfileRecoveryJobUpdate, VoiceRealCallProfileRecoveryLoopAction, VoiceRealCallProfileRecoveryLoopJob, VoiceRealCallProfileRecoveryLoopJobResult, VoiceRealCallProfileRecoveryLoopOptions, VoiceRealCallProfileRecoveryLoopReport, VoiceRealCallProfileRecoveryLoopStartFailure, VoiceSQLiteRealCallProfileRecoveryJobStoreOptions, VoiceRealCallProfileTraceCollector, VoiceRealCallProfileTraceCollectorEvidenceOptions, VoiceRealCallProfileTraceCollectorOptions, VoiceRealCallProfileTraceEvidenceOptions, VoiceRealCallProfileTraceStoreEvidenceOptions } from './proofTrends';
|
|
39
39
|
export { assertVoiceSloCalibration, buildVoiceSloCalibrationReport, buildVoiceSloReadinessThresholdReport, createVoiceSloReadinessThresholdOptions, createVoiceSloReadinessThresholdRoutes, createVoiceSloThresholdProfile, createVoiceSloCalibrationRoutes, renderVoiceSloCalibrationMarkdown, renderVoiceSloReadinessThresholdHTML, renderVoiceSloReadinessThresholdMarkdown } from './sloCalibration';
|
|
40
40
|
export type { VoiceSloCalibrationMetricKey, VoiceSloCalibrationOptions, VoiceSloCalibrationReport, VoiceSloCalibrationRoutesOptions, VoiceSloCalibrationSample, VoiceSloCalibrationStatus, VoiceSloCalibrationThreshold, VoiceSloCalibrationThresholds, VoiceSloReadinessThresholdReport, VoiceSloReadinessThresholdReportOptions, VoiceSloReadinessThresholdOptions, VoiceSloReadinessThresholdRoutesOptions, VoiceSloThresholdProfile } from './sloCalibration';
|
|
41
41
|
export { assertVoiceLiveOpsControlEvidence, assertVoiceLiveOpsEvidence, buildVoiceLiveOpsControlState, createVoiceLiveOpsController, createVoiceLiveOpsRoutes, createVoiceMemoryLiveOpsControlStore, evaluateVoiceLiveOpsControlEvidence, evaluateVoiceLiveOpsEvidence, getVoiceLiveOpsControlStatus, VOICE_LIVE_OPS_ACTIONS } from './liveOps';
|
package/dist/index.js
CHANGED
|
@@ -16155,6 +16155,136 @@ var appendRealCallRecoveryActionQuery = (href, query) => {
|
|
|
16155
16155
|
const search = new URLSearchParams(entries).toString();
|
|
16156
16156
|
return `${base}${separator}${search}${hash ? `#${hash}` : ""}`;
|
|
16157
16157
|
};
|
|
16158
|
+
var sleepVoiceRealCallProfileRecoveryLoop = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
16159
|
+
var describeVoiceRealCallProfileRecoveryLoopAction = (action) => [
|
|
16160
|
+
action.label ?? action.id ?? "recovery action",
|
|
16161
|
+
action.profileId ? `profile=${action.profileId}` : undefined,
|
|
16162
|
+
action.href
|
|
16163
|
+
].filter(Boolean).join(" ");
|
|
16164
|
+
var defaultVoiceRealCallProfileRecoveryLoopActionFilter = (action, readinessCheckLabel) => action.method?.toUpperCase() === "POST" && action.sourceCheckLabel === readinessCheckLabel && typeof action.href === "string" && action.href.length > 0;
|
|
16165
|
+
var uniqueVoiceRealCallProfileRecoveryLoopActions = (actions) => {
|
|
16166
|
+
const seen = new Set;
|
|
16167
|
+
return actions.filter((action) => {
|
|
16168
|
+
const key = `${action.method?.toUpperCase() ?? "GET"} ${action.href ?? ""}`;
|
|
16169
|
+
if (seen.has(key)) {
|
|
16170
|
+
return false;
|
|
16171
|
+
}
|
|
16172
|
+
seen.add(key);
|
|
16173
|
+
return true;
|
|
16174
|
+
});
|
|
16175
|
+
};
|
|
16176
|
+
var runVoiceRealCallProfileRecoveryLoop = async (options) => {
|
|
16177
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
16178
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
|
|
16179
|
+
const jobPollMs = options.jobPollMs ?? 1200;
|
|
16180
|
+
const jobTimeoutMs = options.jobTimeoutMs ?? 600000;
|
|
16181
|
+
const readinessCheckLabel = options.readinessCheckLabel ?? "Real-call profile history";
|
|
16182
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
16183
|
+
const recoveryActionsHref = options.recoveryActionsHref ?? "/api/production-readiness/recovery-actions";
|
|
16184
|
+
const readinessHref = options.readinessHref ?? "/api/production-readiness";
|
|
16185
|
+
const refreshHref = options.refreshHref === undefined ? "/api/voice/real-call-profile-history/refresh" : options.refreshHref;
|
|
16186
|
+
const jobHref = options.jobHref ?? "/api/voice/real-call-profile-history/actions";
|
|
16187
|
+
const toAbsoluteUrl = (href) => new URL(href, baseUrl).toString();
|
|
16188
|
+
const parseJson = async (response) => {
|
|
16189
|
+
const text = await response.text();
|
|
16190
|
+
try {
|
|
16191
|
+
return JSON.parse(text);
|
|
16192
|
+
} catch (error) {
|
|
16193
|
+
throw new Error(`Expected JSON from ${response.url}, got: ${text.slice(0, 300)}`, { cause: error });
|
|
16194
|
+
}
|
|
16195
|
+
};
|
|
16196
|
+
const fetchJson = async (href, init) => {
|
|
16197
|
+
const response = await fetchImpl(toAbsoluteUrl(href), {
|
|
16198
|
+
headers: { accept: "application/json", ...init?.headers },
|
|
16199
|
+
...init,
|
|
16200
|
+
signal: init?.signal ?? AbortSignal.timeout(requestTimeoutMs)
|
|
16201
|
+
});
|
|
16202
|
+
if (!response.ok) {
|
|
16203
|
+
throw new Error(`${href} returned HTTP ${String(response.status)}.`);
|
|
16204
|
+
}
|
|
16205
|
+
return parseJson(response);
|
|
16206
|
+
};
|
|
16207
|
+
const resolveJobHref = (jobId) => typeof jobHref === "function" ? jobHref(jobId) : `${jobHref.replace(/\/$/, "")}/${jobId}`;
|
|
16208
|
+
const getGate = async (fresh = false) => {
|
|
16209
|
+
const href = fresh ? `${readinessHref}${readinessHref.includes("?") ? "&" : "?"}voiceRecoveryLoopFresh=${String(Date.now())}` : readinessHref;
|
|
16210
|
+
const readiness = await fetchJson(href);
|
|
16211
|
+
return readiness.checks?.find((check) => check.label === readinessCheckLabel) ?? null;
|
|
16212
|
+
};
|
|
16213
|
+
const actionsResponse = await fetchJson(recoveryActionsHref);
|
|
16214
|
+
const actionFilter = options.actionFilter ?? ((action) => defaultVoiceRealCallProfileRecoveryLoopActionFilter(action, readinessCheckLabel));
|
|
16215
|
+
const actions = uniqueVoiceRealCallProfileRecoveryLoopActions((actionsResponse.actions ?? []).filter(actionFilter));
|
|
16216
|
+
if (actions.length === 0) {
|
|
16217
|
+
const realCallProfileGate2 = await getGate();
|
|
16218
|
+
return {
|
|
16219
|
+
actionCount: 0,
|
|
16220
|
+
actions,
|
|
16221
|
+
jobs: [],
|
|
16222
|
+
ok: realCallProfileGate2?.status === "pass",
|
|
16223
|
+
realCallProfileGate: realCallProfileGate2,
|
|
16224
|
+
startFailures: []
|
|
16225
|
+
};
|
|
16226
|
+
}
|
|
16227
|
+
options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel.`);
|
|
16228
|
+
for (const action of actions) {
|
|
16229
|
+
options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
|
|
16230
|
+
}
|
|
16231
|
+
const starts = await Promise.allSettled(actions.map(async (action) => {
|
|
16232
|
+
if (!action.href) {
|
|
16233
|
+
throw new Error("Recovery action is missing href.");
|
|
16234
|
+
}
|
|
16235
|
+
const body = await fetchJson(action.href, { method: "POST" });
|
|
16236
|
+
return { action, ...body };
|
|
16237
|
+
}));
|
|
16238
|
+
const startedJobs = starts.flatMap((result) => {
|
|
16239
|
+
if (result.status === "rejected") {
|
|
16240
|
+
return [];
|
|
16241
|
+
}
|
|
16242
|
+
return result.value.jobId ? [result.value] : [];
|
|
16243
|
+
});
|
|
16244
|
+
const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
|
|
16245
|
+
{
|
|
16246
|
+
action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
|
|
16247
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
16248
|
+
}
|
|
16249
|
+
] : []);
|
|
16250
|
+
const pollJob = async (jobId) => {
|
|
16251
|
+
const deadline = Date.now() + jobTimeoutMs;
|
|
16252
|
+
while (Date.now() < deadline) {
|
|
16253
|
+
const body = await fetchJson(resolveJobHref(jobId));
|
|
16254
|
+
const job = body.job;
|
|
16255
|
+
if (!job) {
|
|
16256
|
+
throw new Error(`Recovery job ${jobId} was not found.`);
|
|
16257
|
+
}
|
|
16258
|
+
if (job.status === "pass" || job.status === "fail") {
|
|
16259
|
+
return job;
|
|
16260
|
+
}
|
|
16261
|
+
await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
|
|
16262
|
+
}
|
|
16263
|
+
throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
|
|
16264
|
+
};
|
|
16265
|
+
options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
|
|
16266
|
+
const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
|
|
16267
|
+
const jobs = jobResults.map((result, index) => ({
|
|
16268
|
+
action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
|
|
16269
|
+
jobId: startedJobs[index]?.jobId,
|
|
16270
|
+
result: result.status === "fulfilled" ? result.value : {
|
|
16271
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason),
|
|
16272
|
+
status: "fail"
|
|
16273
|
+
}
|
|
16274
|
+
}));
|
|
16275
|
+
if (refreshHref !== false) {
|
|
16276
|
+
await fetchJson(refreshHref, { method: "POST" });
|
|
16277
|
+
}
|
|
16278
|
+
const realCallProfileGate = await getGate(true);
|
|
16279
|
+
return {
|
|
16280
|
+
actionCount: actions.length,
|
|
16281
|
+
actions,
|
|
16282
|
+
jobs,
|
|
16283
|
+
ok: startFailures.length === 0 && jobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
|
|
16284
|
+
realCallProfileGate,
|
|
16285
|
+
startFailures
|
|
16286
|
+
};
|
|
16287
|
+
};
|
|
16158
16288
|
var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
|
|
16159
16289
|
const actions = [
|
|
16160
16290
|
{
|
|
@@ -39019,6 +39149,7 @@ export {
|
|
|
39019
39149
|
runVoiceScenarioFixtureEvals,
|
|
39020
39150
|
runVoiceScenarioEvals,
|
|
39021
39151
|
runVoiceReconnectContract,
|
|
39152
|
+
runVoiceRealCallProfileRecoveryLoop,
|
|
39022
39153
|
runVoiceProviderRoutingContract,
|
|
39023
39154
|
runVoiceProfileSwitchPolicyProof,
|
|
39024
39155
|
runVoicePhoneAgentProductionSmokeContract,
|
package/dist/proofTrends.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Elysia } from 'elysia';
|
|
2
2
|
import type { Database } from 'bun:sqlite';
|
|
3
|
-
import type { VoiceProductionReadinessAction, VoiceProductionReadinessCheck } from './productionReadiness';
|
|
3
|
+
import type { VoiceProductionReadinessAction, VoiceProductionReadinessCheck, VoiceReadinessRecoveryAction } from './productionReadiness';
|
|
4
4
|
import type { StoredVoiceTraceEvent, VoiceTraceEventStore } from './trace';
|
|
5
5
|
export type VoiceProofTrendStatus = 'empty' | 'fail' | 'pass' | 'stale';
|
|
6
6
|
export type VoiceProofTrendSummary = {
|
|
@@ -469,6 +469,46 @@ export type VoiceRealCallProfileRecoveryActionRoutesOptions = VoiceRealCallProfi
|
|
|
469
469
|
handlers?: Partial<Record<VoiceRealCallProfileRecoveryActionId, VoiceRealCallProfileRecoveryActionHandler>>;
|
|
470
470
|
jobStore?: VoiceRealCallProfileRecoveryJobStore;
|
|
471
471
|
};
|
|
472
|
+
export type VoiceRealCallProfileRecoveryLoopAction = Partial<VoiceReadinessRecoveryAction> & Partial<VoiceRealCallProfileRecoveryAction> & {
|
|
473
|
+
href?: string;
|
|
474
|
+
method?: string;
|
|
475
|
+
};
|
|
476
|
+
export type VoiceRealCallProfileRecoveryLoopJob = Partial<VoiceRealCallProfileRecoveryJob> & {
|
|
477
|
+
error?: string;
|
|
478
|
+
id?: string;
|
|
479
|
+
status?: string;
|
|
480
|
+
};
|
|
481
|
+
export type VoiceRealCallProfileRecoveryLoopJobResult = {
|
|
482
|
+
action: string;
|
|
483
|
+
jobId?: string;
|
|
484
|
+
result: VoiceRealCallProfileRecoveryLoopJob;
|
|
485
|
+
};
|
|
486
|
+
export type VoiceRealCallProfileRecoveryLoopStartFailure = {
|
|
487
|
+
action: string;
|
|
488
|
+
error: string;
|
|
489
|
+
};
|
|
490
|
+
export type VoiceRealCallProfileRecoveryLoopReport = {
|
|
491
|
+
actionCount: number;
|
|
492
|
+
actions: VoiceRealCallProfileRecoveryLoopAction[];
|
|
493
|
+
jobs: VoiceRealCallProfileRecoveryLoopJobResult[];
|
|
494
|
+
ok: boolean;
|
|
495
|
+
realCallProfileGate: VoiceProductionReadinessCheck | null;
|
|
496
|
+
startFailures: VoiceRealCallProfileRecoveryLoopStartFailure[];
|
|
497
|
+
};
|
|
498
|
+
export type VoiceRealCallProfileRecoveryLoopOptions = {
|
|
499
|
+
actionFilter?: (action: VoiceRealCallProfileRecoveryLoopAction) => boolean;
|
|
500
|
+
baseUrl: string;
|
|
501
|
+
fetch?: typeof fetch;
|
|
502
|
+
jobHref?: string | ((jobId: string) => string);
|
|
503
|
+
jobPollMs?: number;
|
|
504
|
+
jobTimeoutMs?: number;
|
|
505
|
+
logger?: Pick<Console, 'log'>;
|
|
506
|
+
readinessCheckLabel?: string;
|
|
507
|
+
readinessHref?: string;
|
|
508
|
+
recoveryActionsHref?: string;
|
|
509
|
+
refreshHref?: false | string;
|
|
510
|
+
requestTimeoutMs?: number;
|
|
511
|
+
};
|
|
472
512
|
export declare const DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS: number;
|
|
473
513
|
export declare const DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS: ({
|
|
474
514
|
description: string;
|
|
@@ -503,6 +543,7 @@ export declare const createVoiceRealCallProfileTraceCollector: <TEvent extends S
|
|
|
503
543
|
export declare const buildVoiceProofTrendProfileSummaries: (input: VoiceProofTrendReport | readonly VoiceProofTrendReport[], options?: VoiceProofTrendProfileSummaryOptions) => VoiceProofTrendProfileSummary[];
|
|
504
544
|
export declare const buildVoiceProofTrendReportFromRealCallProfiles: (options: VoiceProofTrendRealCallProfileReportOptions) => VoiceProofTrendReport;
|
|
505
545
|
export declare const buildVoiceRealCallProfileDefaults: (input: VoiceRealCallProfileHistoryReport | VoiceProofTrendReport, options?: VoiceRealCallProfileDefaultsOptions) => VoiceRealCallProfileDefaultsReport;
|
|
546
|
+
export declare const runVoiceRealCallProfileRecoveryLoop: (options: VoiceRealCallProfileRecoveryLoopOptions) => Promise<VoiceRealCallProfileRecoveryLoopReport>;
|
|
506
547
|
export declare const buildVoiceRealCallProfileRecoveryActions: (report: VoiceRealCallProfileHistoryReport, options?: VoiceRealCallProfileRecoveryActionOptions) => VoiceRealCallProfileRecoveryAction[];
|
|
507
548
|
export declare const createVoiceInMemoryRealCallProfileRecoveryJobStore: (options?: {
|
|
508
549
|
idPrefix?: string;
|
package/dist/react/index.js
CHANGED
|
@@ -2273,6 +2273,136 @@ var appendRealCallRecoveryActionQuery = (href, query) => {
|
|
|
2273
2273
|
const search = new URLSearchParams(entries).toString();
|
|
2274
2274
|
return `${base}${separator}${search}${hash ? `#${hash}` : ""}`;
|
|
2275
2275
|
};
|
|
2276
|
+
var sleepVoiceRealCallProfileRecoveryLoop = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2277
|
+
var describeVoiceRealCallProfileRecoveryLoopAction = (action) => [
|
|
2278
|
+
action.label ?? action.id ?? "recovery action",
|
|
2279
|
+
action.profileId ? `profile=${action.profileId}` : undefined,
|
|
2280
|
+
action.href
|
|
2281
|
+
].filter(Boolean).join(" ");
|
|
2282
|
+
var defaultVoiceRealCallProfileRecoveryLoopActionFilter = (action, readinessCheckLabel) => action.method?.toUpperCase() === "POST" && action.sourceCheckLabel === readinessCheckLabel && typeof action.href === "string" && action.href.length > 0;
|
|
2283
|
+
var uniqueVoiceRealCallProfileRecoveryLoopActions = (actions) => {
|
|
2284
|
+
const seen = new Set;
|
|
2285
|
+
return actions.filter((action) => {
|
|
2286
|
+
const key = `${action.method?.toUpperCase() ?? "GET"} ${action.href ?? ""}`;
|
|
2287
|
+
if (seen.has(key)) {
|
|
2288
|
+
return false;
|
|
2289
|
+
}
|
|
2290
|
+
seen.add(key);
|
|
2291
|
+
return true;
|
|
2292
|
+
});
|
|
2293
|
+
};
|
|
2294
|
+
var runVoiceRealCallProfileRecoveryLoop = async (options) => {
|
|
2295
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
2296
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
|
|
2297
|
+
const jobPollMs = options.jobPollMs ?? 1200;
|
|
2298
|
+
const jobTimeoutMs = options.jobTimeoutMs ?? 600000;
|
|
2299
|
+
const readinessCheckLabel = options.readinessCheckLabel ?? "Real-call profile history";
|
|
2300
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
2301
|
+
const recoveryActionsHref = options.recoveryActionsHref ?? "/api/production-readiness/recovery-actions";
|
|
2302
|
+
const readinessHref = options.readinessHref ?? "/api/production-readiness";
|
|
2303
|
+
const refreshHref = options.refreshHref === undefined ? "/api/voice/real-call-profile-history/refresh" : options.refreshHref;
|
|
2304
|
+
const jobHref = options.jobHref ?? "/api/voice/real-call-profile-history/actions";
|
|
2305
|
+
const toAbsoluteUrl = (href) => new URL(href, baseUrl).toString();
|
|
2306
|
+
const parseJson = async (response) => {
|
|
2307
|
+
const text = await response.text();
|
|
2308
|
+
try {
|
|
2309
|
+
return JSON.parse(text);
|
|
2310
|
+
} catch (error) {
|
|
2311
|
+
throw new Error(`Expected JSON from ${response.url}, got: ${text.slice(0, 300)}`, { cause: error });
|
|
2312
|
+
}
|
|
2313
|
+
};
|
|
2314
|
+
const fetchJson = async (href, init) => {
|
|
2315
|
+
const response = await fetchImpl(toAbsoluteUrl(href), {
|
|
2316
|
+
headers: { accept: "application/json", ...init?.headers },
|
|
2317
|
+
...init,
|
|
2318
|
+
signal: init?.signal ?? AbortSignal.timeout(requestTimeoutMs)
|
|
2319
|
+
});
|
|
2320
|
+
if (!response.ok) {
|
|
2321
|
+
throw new Error(`${href} returned HTTP ${String(response.status)}.`);
|
|
2322
|
+
}
|
|
2323
|
+
return parseJson(response);
|
|
2324
|
+
};
|
|
2325
|
+
const resolveJobHref = (jobId) => typeof jobHref === "function" ? jobHref(jobId) : `${jobHref.replace(/\/$/, "")}/${jobId}`;
|
|
2326
|
+
const getGate = async (fresh = false) => {
|
|
2327
|
+
const href = fresh ? `${readinessHref}${readinessHref.includes("?") ? "&" : "?"}voiceRecoveryLoopFresh=${String(Date.now())}` : readinessHref;
|
|
2328
|
+
const readiness = await fetchJson(href);
|
|
2329
|
+
return readiness.checks?.find((check) => check.label === readinessCheckLabel) ?? null;
|
|
2330
|
+
};
|
|
2331
|
+
const actionsResponse = await fetchJson(recoveryActionsHref);
|
|
2332
|
+
const actionFilter = options.actionFilter ?? ((action) => defaultVoiceRealCallProfileRecoveryLoopActionFilter(action, readinessCheckLabel));
|
|
2333
|
+
const actions = uniqueVoiceRealCallProfileRecoveryLoopActions((actionsResponse.actions ?? []).filter(actionFilter));
|
|
2334
|
+
if (actions.length === 0) {
|
|
2335
|
+
const realCallProfileGate2 = await getGate();
|
|
2336
|
+
return {
|
|
2337
|
+
actionCount: 0,
|
|
2338
|
+
actions,
|
|
2339
|
+
jobs: [],
|
|
2340
|
+
ok: realCallProfileGate2?.status === "pass",
|
|
2341
|
+
realCallProfileGate: realCallProfileGate2,
|
|
2342
|
+
startFailures: []
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel.`);
|
|
2346
|
+
for (const action of actions) {
|
|
2347
|
+
options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
|
|
2348
|
+
}
|
|
2349
|
+
const starts = await Promise.allSettled(actions.map(async (action) => {
|
|
2350
|
+
if (!action.href) {
|
|
2351
|
+
throw new Error("Recovery action is missing href.");
|
|
2352
|
+
}
|
|
2353
|
+
const body = await fetchJson(action.href, { method: "POST" });
|
|
2354
|
+
return { action, ...body };
|
|
2355
|
+
}));
|
|
2356
|
+
const startedJobs = starts.flatMap((result) => {
|
|
2357
|
+
if (result.status === "rejected") {
|
|
2358
|
+
return [];
|
|
2359
|
+
}
|
|
2360
|
+
return result.value.jobId ? [result.value] : [];
|
|
2361
|
+
});
|
|
2362
|
+
const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
|
|
2363
|
+
{
|
|
2364
|
+
action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
|
|
2365
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
2366
|
+
}
|
|
2367
|
+
] : []);
|
|
2368
|
+
const pollJob = async (jobId) => {
|
|
2369
|
+
const deadline = Date.now() + jobTimeoutMs;
|
|
2370
|
+
while (Date.now() < deadline) {
|
|
2371
|
+
const body = await fetchJson(resolveJobHref(jobId));
|
|
2372
|
+
const job = body.job;
|
|
2373
|
+
if (!job) {
|
|
2374
|
+
throw new Error(`Recovery job ${jobId} was not found.`);
|
|
2375
|
+
}
|
|
2376
|
+
if (job.status === "pass" || job.status === "fail") {
|
|
2377
|
+
return job;
|
|
2378
|
+
}
|
|
2379
|
+
await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
|
|
2380
|
+
}
|
|
2381
|
+
throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
|
|
2382
|
+
};
|
|
2383
|
+
options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
|
|
2384
|
+
const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
|
|
2385
|
+
const jobs = jobResults.map((result, index) => ({
|
|
2386
|
+
action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
|
|
2387
|
+
jobId: startedJobs[index]?.jobId,
|
|
2388
|
+
result: result.status === "fulfilled" ? result.value : {
|
|
2389
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason),
|
|
2390
|
+
status: "fail"
|
|
2391
|
+
}
|
|
2392
|
+
}));
|
|
2393
|
+
if (refreshHref !== false) {
|
|
2394
|
+
await fetchJson(refreshHref, { method: "POST" });
|
|
2395
|
+
}
|
|
2396
|
+
const realCallProfileGate = await getGate(true);
|
|
2397
|
+
return {
|
|
2398
|
+
actionCount: actions.length,
|
|
2399
|
+
actions,
|
|
2400
|
+
jobs,
|
|
2401
|
+
ok: startFailures.length === 0 && jobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
|
|
2402
|
+
realCallProfileGate,
|
|
2403
|
+
startFailures
|
|
2404
|
+
};
|
|
2405
|
+
};
|
|
2276
2406
|
var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
|
|
2277
2407
|
const actions = [
|
|
2278
2408
|
{
|
package/dist/vue/index.js
CHANGED
|
@@ -2194,6 +2194,136 @@ var appendRealCallRecoveryActionQuery = (href, query) => {
|
|
|
2194
2194
|
const search = new URLSearchParams(entries).toString();
|
|
2195
2195
|
return `${base}${separator}${search}${hash ? `#${hash}` : ""}`;
|
|
2196
2196
|
};
|
|
2197
|
+
var sleepVoiceRealCallProfileRecoveryLoop = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2198
|
+
var describeVoiceRealCallProfileRecoveryLoopAction = (action) => [
|
|
2199
|
+
action.label ?? action.id ?? "recovery action",
|
|
2200
|
+
action.profileId ? `profile=${action.profileId}` : undefined,
|
|
2201
|
+
action.href
|
|
2202
|
+
].filter(Boolean).join(" ");
|
|
2203
|
+
var defaultVoiceRealCallProfileRecoveryLoopActionFilter = (action, readinessCheckLabel) => action.method?.toUpperCase() === "POST" && action.sourceCheckLabel === readinessCheckLabel && typeof action.href === "string" && action.href.length > 0;
|
|
2204
|
+
var uniqueVoiceRealCallProfileRecoveryLoopActions = (actions) => {
|
|
2205
|
+
const seen = new Set;
|
|
2206
|
+
return actions.filter((action) => {
|
|
2207
|
+
const key = `${action.method?.toUpperCase() ?? "GET"} ${action.href ?? ""}`;
|
|
2208
|
+
if (seen.has(key)) {
|
|
2209
|
+
return false;
|
|
2210
|
+
}
|
|
2211
|
+
seen.add(key);
|
|
2212
|
+
return true;
|
|
2213
|
+
});
|
|
2214
|
+
};
|
|
2215
|
+
var runVoiceRealCallProfileRecoveryLoop = async (options) => {
|
|
2216
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
2217
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
|
|
2218
|
+
const jobPollMs = options.jobPollMs ?? 1200;
|
|
2219
|
+
const jobTimeoutMs = options.jobTimeoutMs ?? 600000;
|
|
2220
|
+
const readinessCheckLabel = options.readinessCheckLabel ?? "Real-call profile history";
|
|
2221
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
2222
|
+
const recoveryActionsHref = options.recoveryActionsHref ?? "/api/production-readiness/recovery-actions";
|
|
2223
|
+
const readinessHref = options.readinessHref ?? "/api/production-readiness";
|
|
2224
|
+
const refreshHref = options.refreshHref === undefined ? "/api/voice/real-call-profile-history/refresh" : options.refreshHref;
|
|
2225
|
+
const jobHref = options.jobHref ?? "/api/voice/real-call-profile-history/actions";
|
|
2226
|
+
const toAbsoluteUrl = (href) => new URL(href, baseUrl).toString();
|
|
2227
|
+
const parseJson = async (response) => {
|
|
2228
|
+
const text = await response.text();
|
|
2229
|
+
try {
|
|
2230
|
+
return JSON.parse(text);
|
|
2231
|
+
} catch (error) {
|
|
2232
|
+
throw new Error(`Expected JSON from ${response.url}, got: ${text.slice(0, 300)}`, { cause: error });
|
|
2233
|
+
}
|
|
2234
|
+
};
|
|
2235
|
+
const fetchJson = async (href, init) => {
|
|
2236
|
+
const response = await fetchImpl(toAbsoluteUrl(href), {
|
|
2237
|
+
headers: { accept: "application/json", ...init?.headers },
|
|
2238
|
+
...init,
|
|
2239
|
+
signal: init?.signal ?? AbortSignal.timeout(requestTimeoutMs)
|
|
2240
|
+
});
|
|
2241
|
+
if (!response.ok) {
|
|
2242
|
+
throw new Error(`${href} returned HTTP ${String(response.status)}.`);
|
|
2243
|
+
}
|
|
2244
|
+
return parseJson(response);
|
|
2245
|
+
};
|
|
2246
|
+
const resolveJobHref = (jobId) => typeof jobHref === "function" ? jobHref(jobId) : `${jobHref.replace(/\/$/, "")}/${jobId}`;
|
|
2247
|
+
const getGate = async (fresh = false) => {
|
|
2248
|
+
const href = fresh ? `${readinessHref}${readinessHref.includes("?") ? "&" : "?"}voiceRecoveryLoopFresh=${String(Date.now())}` : readinessHref;
|
|
2249
|
+
const readiness = await fetchJson(href);
|
|
2250
|
+
return readiness.checks?.find((check) => check.label === readinessCheckLabel) ?? null;
|
|
2251
|
+
};
|
|
2252
|
+
const actionsResponse = await fetchJson(recoveryActionsHref);
|
|
2253
|
+
const actionFilter = options.actionFilter ?? ((action) => defaultVoiceRealCallProfileRecoveryLoopActionFilter(action, readinessCheckLabel));
|
|
2254
|
+
const actions = uniqueVoiceRealCallProfileRecoveryLoopActions((actionsResponse.actions ?? []).filter(actionFilter));
|
|
2255
|
+
if (actions.length === 0) {
|
|
2256
|
+
const realCallProfileGate2 = await getGate();
|
|
2257
|
+
return {
|
|
2258
|
+
actionCount: 0,
|
|
2259
|
+
actions,
|
|
2260
|
+
jobs: [],
|
|
2261
|
+
ok: realCallProfileGate2?.status === "pass",
|
|
2262
|
+
realCallProfileGate: realCallProfileGate2,
|
|
2263
|
+
startFailures: []
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel.`);
|
|
2267
|
+
for (const action of actions) {
|
|
2268
|
+
options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
|
|
2269
|
+
}
|
|
2270
|
+
const starts = await Promise.allSettled(actions.map(async (action) => {
|
|
2271
|
+
if (!action.href) {
|
|
2272
|
+
throw new Error("Recovery action is missing href.");
|
|
2273
|
+
}
|
|
2274
|
+
const body = await fetchJson(action.href, { method: "POST" });
|
|
2275
|
+
return { action, ...body };
|
|
2276
|
+
}));
|
|
2277
|
+
const startedJobs = starts.flatMap((result) => {
|
|
2278
|
+
if (result.status === "rejected") {
|
|
2279
|
+
return [];
|
|
2280
|
+
}
|
|
2281
|
+
return result.value.jobId ? [result.value] : [];
|
|
2282
|
+
});
|
|
2283
|
+
const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
|
|
2284
|
+
{
|
|
2285
|
+
action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
|
|
2286
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
2287
|
+
}
|
|
2288
|
+
] : []);
|
|
2289
|
+
const pollJob = async (jobId) => {
|
|
2290
|
+
const deadline = Date.now() + jobTimeoutMs;
|
|
2291
|
+
while (Date.now() < deadline) {
|
|
2292
|
+
const body = await fetchJson(resolveJobHref(jobId));
|
|
2293
|
+
const job = body.job;
|
|
2294
|
+
if (!job) {
|
|
2295
|
+
throw new Error(`Recovery job ${jobId} was not found.`);
|
|
2296
|
+
}
|
|
2297
|
+
if (job.status === "pass" || job.status === "fail") {
|
|
2298
|
+
return job;
|
|
2299
|
+
}
|
|
2300
|
+
await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
|
|
2301
|
+
}
|
|
2302
|
+
throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
|
|
2303
|
+
};
|
|
2304
|
+
options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
|
|
2305
|
+
const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
|
|
2306
|
+
const jobs = jobResults.map((result, index) => ({
|
|
2307
|
+
action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
|
|
2308
|
+
jobId: startedJobs[index]?.jobId,
|
|
2309
|
+
result: result.status === "fulfilled" ? result.value : {
|
|
2310
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason),
|
|
2311
|
+
status: "fail"
|
|
2312
|
+
}
|
|
2313
|
+
}));
|
|
2314
|
+
if (refreshHref !== false) {
|
|
2315
|
+
await fetchJson(refreshHref, { method: "POST" });
|
|
2316
|
+
}
|
|
2317
|
+
const realCallProfileGate = await getGate(true);
|
|
2318
|
+
return {
|
|
2319
|
+
actionCount: actions.length,
|
|
2320
|
+
actions,
|
|
2321
|
+
jobs,
|
|
2322
|
+
ok: startFailures.length === 0 && jobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
|
|
2323
|
+
realCallProfileGate,
|
|
2324
|
+
startFailures
|
|
2325
|
+
};
|
|
2326
|
+
};
|
|
2197
2327
|
var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
|
|
2198
2328
|
const actions = [
|
|
2199
2329
|
{
|