@absolutejs/voice 0.0.22-beta.387 → 0.0.22-beta.389

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.
@@ -6557,6 +6557,8 @@ var runVoiceRealCallProfileRecoveryLoop = async (options) => {
6557
6557
  const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
6558
6558
  const jobPollMs = options.jobPollMs ?? 1200;
6559
6559
  const jobTimeoutMs = options.jobTimeoutMs ?? 600000;
6560
+ const interPassDelayMs = typeof options.interPassDelayMs === "number" && Number.isFinite(options.interPassDelayMs) && options.interPassDelayMs > 0 ? options.interPassDelayMs : 5000;
6561
+ const maxPasses = typeof options.maxPasses === "number" && Number.isFinite(options.maxPasses) && options.maxPasses > 0 ? Math.floor(options.maxPasses) : 3;
6560
6562
  const readinessCheckLabel = options.readinessCheckLabel ?? "Real-call profile history";
6561
6563
  const fetchImpl = options.fetch ?? fetch;
6562
6564
  const recoveryActionsHref = options.recoveryActionsHref ?? "/api/production-readiness/recovery-actions";
@@ -6598,70 +6600,88 @@ var runVoiceRealCallProfileRecoveryLoop = async (options) => {
6598
6600
  actionCount: 0,
6599
6601
  actions,
6600
6602
  jobs: [],
6603
+ passes: 0,
6601
6604
  ok: realCallProfileGate2?.status === "pass",
6602
6605
  realCallProfileGate: realCallProfileGate2,
6603
6606
  startFailures: []
6604
6607
  };
6605
6608
  }
6606
- options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel.`);
6607
- for (const action of actions) {
6608
- options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
6609
- }
6610
- const starts = await Promise.allSettled(actions.map(async (action) => {
6611
- if (!action.href) {
6612
- throw new Error("Recovery action is missing href.");
6613
- }
6614
- const body = await fetchJson(action.href, { method: "POST" });
6615
- return { action, ...body };
6616
- }));
6617
- const startedJobs = starts.flatMap((result) => {
6618
- if (result.status === "rejected") {
6619
- return [];
6620
- }
6621
- return result.value.jobId ? [result.value] : [];
6622
- });
6623
- const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
6624
- {
6625
- action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
6626
- error: result.reason instanceof Error ? result.reason.message : String(result.reason)
6627
- }
6628
- ] : []);
6629
- const pollJob = async (jobId) => {
6630
- const deadline = Date.now() + jobTimeoutMs;
6631
- while (Date.now() < deadline) {
6632
- const body = await fetchJson(resolveJobHref(jobId));
6633
- const job = body.job;
6634
- if (!job) {
6635
- throw new Error(`Recovery job ${jobId} was not found.`);
6609
+ const allJobs = [];
6610
+ const allStartFailures = [];
6611
+ let realCallProfileGate = null;
6612
+ let passes = 0;
6613
+ for (let pass = 1;pass <= maxPasses; pass += 1) {
6614
+ passes = pass;
6615
+ options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel (pass ${String(pass)}/${String(maxPasses)}).`);
6616
+ for (const action of actions) {
6617
+ options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
6618
+ }
6619
+ const starts = await Promise.allSettled(actions.map(async (action) => {
6620
+ if (!action.href) {
6621
+ throw new Error("Recovery action is missing href.");
6622
+ }
6623
+ const body = await fetchJson(action.href, { method: "POST" });
6624
+ return { action, ...body };
6625
+ }));
6626
+ const startedJobs = starts.flatMap((result) => {
6627
+ if (result.status === "rejected") {
6628
+ return [];
6629
+ }
6630
+ return result.value.jobId ? [result.value] : [];
6631
+ });
6632
+ const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
6633
+ {
6634
+ action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
6635
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason)
6636
+ }
6637
+ ] : []);
6638
+ allStartFailures.push(...startFailures);
6639
+ const pollJob = async (jobId) => {
6640
+ const deadline = Date.now() + jobTimeoutMs;
6641
+ while (Date.now() < deadline) {
6642
+ const body = await fetchJson(resolveJobHref(jobId));
6643
+ const job = body.job;
6644
+ if (!job) {
6645
+ throw new Error(`Recovery job ${jobId} was not found.`);
6646
+ }
6647
+ if (job.status === "pass" || job.status === "fail") {
6648
+ return job;
6649
+ }
6650
+ await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
6636
6651
  }
6637
- if (job.status === "pass" || job.status === "fail") {
6638
- return job;
6652
+ throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
6653
+ };
6654
+ options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
6655
+ const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
6656
+ const jobs = jobResults.map((result, index) => ({
6657
+ action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
6658
+ jobId: startedJobs[index]?.jobId,
6659
+ result: result.status === "fulfilled" ? result.value : {
6660
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason),
6661
+ status: "fail"
6639
6662
  }
6640
- await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
6663
+ }));
6664
+ allJobs.push(...jobs);
6665
+ if (refreshHref !== false) {
6666
+ await fetchJson(refreshHref, { method: "POST" });
6641
6667
  }
6642
- throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
6643
- };
6644
- options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
6645
- const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
6646
- const jobs = jobResults.map((result, index) => ({
6647
- action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
6648
- jobId: startedJobs[index]?.jobId,
6649
- result: result.status === "fulfilled" ? result.value : {
6650
- error: result.reason instanceof Error ? result.reason.message : String(result.reason),
6651
- status: "fail"
6668
+ realCallProfileGate = await getGate(true);
6669
+ if (realCallProfileGate?.status === "pass") {
6670
+ break;
6671
+ }
6672
+ if (pass < maxPasses) {
6673
+ options.logger?.log(`Waiting ${String(interPassDelayMs)}ms before the next real-call profile recovery pass.`);
6674
+ await sleepVoiceRealCallProfileRecoveryLoop(interPassDelayMs);
6652
6675
  }
6653
- }));
6654
- if (refreshHref !== false) {
6655
- await fetchJson(refreshHref, { method: "POST" });
6656
6676
  }
6657
- const realCallProfileGate = await getGate(true);
6658
6677
  return {
6659
- actionCount: actions.length,
6678
+ actionCount: actions.length * passes,
6660
6679
  actions,
6661
- jobs,
6662
- ok: startFailures.length === 0 && jobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
6680
+ jobs: allJobs,
6681
+ passes,
6682
+ ok: allStartFailures.length === 0 && allJobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
6663
6683
  realCallProfileGate,
6664
- startFailures
6684
+ startFailures: allStartFailures
6665
6685
  };
6666
6686
  };
6667
6687
  var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
package/dist/index.js CHANGED
@@ -17076,6 +17076,8 @@ var runVoiceRealCallProfileRecoveryLoop = async (options) => {
17076
17076
  const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
17077
17077
  const jobPollMs = options.jobPollMs ?? 1200;
17078
17078
  const jobTimeoutMs = options.jobTimeoutMs ?? 600000;
17079
+ const interPassDelayMs = typeof options.interPassDelayMs === "number" && Number.isFinite(options.interPassDelayMs) && options.interPassDelayMs > 0 ? options.interPassDelayMs : 5000;
17080
+ const maxPasses = typeof options.maxPasses === "number" && Number.isFinite(options.maxPasses) && options.maxPasses > 0 ? Math.floor(options.maxPasses) : 3;
17079
17081
  const readinessCheckLabel = options.readinessCheckLabel ?? "Real-call profile history";
17080
17082
  const fetchImpl = options.fetch ?? fetch;
17081
17083
  const recoveryActionsHref = options.recoveryActionsHref ?? "/api/production-readiness/recovery-actions";
@@ -17117,70 +17119,88 @@ var runVoiceRealCallProfileRecoveryLoop = async (options) => {
17117
17119
  actionCount: 0,
17118
17120
  actions,
17119
17121
  jobs: [],
17122
+ passes: 0,
17120
17123
  ok: realCallProfileGate2?.status === "pass",
17121
17124
  realCallProfileGate: realCallProfileGate2,
17122
17125
  startFailures: []
17123
17126
  };
17124
17127
  }
17125
- options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel.`);
17126
- for (const action of actions) {
17127
- options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
17128
- }
17129
- const starts = await Promise.allSettled(actions.map(async (action) => {
17130
- if (!action.href) {
17131
- throw new Error("Recovery action is missing href.");
17128
+ const allJobs = [];
17129
+ const allStartFailures = [];
17130
+ let realCallProfileGate = null;
17131
+ let passes = 0;
17132
+ for (let pass = 1;pass <= maxPasses; pass += 1) {
17133
+ passes = pass;
17134
+ options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel (pass ${String(pass)}/${String(maxPasses)}).`);
17135
+ for (const action of actions) {
17136
+ options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
17137
+ }
17138
+ const starts = await Promise.allSettled(actions.map(async (action) => {
17139
+ if (!action.href) {
17140
+ throw new Error("Recovery action is missing href.");
17141
+ }
17142
+ const body = await fetchJson(action.href, { method: "POST" });
17143
+ return { action, ...body };
17144
+ }));
17145
+ const startedJobs = starts.flatMap((result) => {
17146
+ if (result.status === "rejected") {
17147
+ return [];
17148
+ }
17149
+ return result.value.jobId ? [result.value] : [];
17150
+ });
17151
+ const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
17152
+ {
17153
+ action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
17154
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason)
17155
+ }
17156
+ ] : []);
17157
+ allStartFailures.push(...startFailures);
17158
+ const pollJob = async (jobId) => {
17159
+ const deadline = Date.now() + jobTimeoutMs;
17160
+ while (Date.now() < deadline) {
17161
+ const body = await fetchJson(resolveJobHref(jobId));
17162
+ const job = body.job;
17163
+ if (!job) {
17164
+ throw new Error(`Recovery job ${jobId} was not found.`);
17165
+ }
17166
+ if (job.status === "pass" || job.status === "fail") {
17167
+ return job;
17168
+ }
17169
+ await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
17170
+ }
17171
+ throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
17172
+ };
17173
+ options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
17174
+ const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
17175
+ const jobs = jobResults.map((result, index) => ({
17176
+ action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
17177
+ jobId: startedJobs[index]?.jobId,
17178
+ result: result.status === "fulfilled" ? result.value : {
17179
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason),
17180
+ status: "fail"
17181
+ }
17182
+ }));
17183
+ allJobs.push(...jobs);
17184
+ if (refreshHref !== false) {
17185
+ await fetchJson(refreshHref, { method: "POST" });
17132
17186
  }
17133
- const body = await fetchJson(action.href, { method: "POST" });
17134
- return { action, ...body };
17135
- }));
17136
- const startedJobs = starts.flatMap((result) => {
17137
- if (result.status === "rejected") {
17138
- return [];
17187
+ realCallProfileGate = await getGate(true);
17188
+ if (realCallProfileGate?.status === "pass") {
17189
+ break;
17139
17190
  }
17140
- return result.value.jobId ? [result.value] : [];
17141
- });
17142
- const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
17143
- {
17144
- action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
17145
- error: result.reason instanceof Error ? result.reason.message : String(result.reason)
17146
- }
17147
- ] : []);
17148
- const pollJob = async (jobId) => {
17149
- const deadline = Date.now() + jobTimeoutMs;
17150
- while (Date.now() < deadline) {
17151
- const body = await fetchJson(resolveJobHref(jobId));
17152
- const job = body.job;
17153
- if (!job) {
17154
- throw new Error(`Recovery job ${jobId} was not found.`);
17155
- }
17156
- if (job.status === "pass" || job.status === "fail") {
17157
- return job;
17158
- }
17159
- await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
17160
- }
17161
- throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
17162
- };
17163
- options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
17164
- const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
17165
- const jobs = jobResults.map((result, index) => ({
17166
- action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
17167
- jobId: startedJobs[index]?.jobId,
17168
- result: result.status === "fulfilled" ? result.value : {
17169
- error: result.reason instanceof Error ? result.reason.message : String(result.reason),
17170
- status: "fail"
17191
+ if (pass < maxPasses) {
17192
+ options.logger?.log(`Waiting ${String(interPassDelayMs)}ms before the next real-call profile recovery pass.`);
17193
+ await sleepVoiceRealCallProfileRecoveryLoop(interPassDelayMs);
17171
17194
  }
17172
- }));
17173
- if (refreshHref !== false) {
17174
- await fetchJson(refreshHref, { method: "POST" });
17175
17195
  }
17176
- const realCallProfileGate = await getGate(true);
17177
17196
  return {
17178
- actionCount: actions.length,
17197
+ actionCount: actions.length * passes,
17179
17198
  actions,
17180
- jobs,
17181
- ok: startFailures.length === 0 && jobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
17199
+ jobs: allJobs,
17200
+ passes,
17201
+ ok: allStartFailures.length === 0 && allJobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
17182
17202
  realCallProfileGate,
17183
- startFailures
17203
+ startFailures: allStartFailures
17184
17204
  };
17185
17205
  };
17186
17206
  var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
@@ -492,6 +492,7 @@ export type VoiceRealCallProfileRecoveryLoopReport = {
492
492
  actionCount: number;
493
493
  actions: VoiceRealCallProfileRecoveryLoopAction[];
494
494
  jobs: VoiceRealCallProfileRecoveryLoopJobResult[];
495
+ passes: number;
495
496
  ok: boolean;
496
497
  realCallProfileGate: VoiceProductionReadinessCheck | null;
497
498
  startFailures: VoiceRealCallProfileRecoveryLoopStartFailure[];
@@ -500,10 +501,12 @@ export type VoiceRealCallProfileRecoveryLoopOptions = {
500
501
  actionFilter?: (action: VoiceRealCallProfileRecoveryLoopAction) => boolean;
501
502
  baseUrl: string;
502
503
  fetch?: typeof fetch;
504
+ interPassDelayMs?: number;
503
505
  jobHref?: string | ((jobId: string) => string);
504
506
  jobPollMs?: number;
505
507
  jobTimeoutMs?: number;
506
508
  logger?: Pick<Console, 'log'>;
509
+ maxPasses?: number;
507
510
  readinessCheckLabel?: string;
508
511
  readinessHref?: string;
509
512
  recoveryActionsHref?: string;
@@ -4144,6 +4144,8 @@ var runVoiceRealCallProfileRecoveryLoop = async (options) => {
4144
4144
  const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
4145
4145
  const jobPollMs = options.jobPollMs ?? 1200;
4146
4146
  const jobTimeoutMs = options.jobTimeoutMs ?? 600000;
4147
+ const interPassDelayMs = typeof options.interPassDelayMs === "number" && Number.isFinite(options.interPassDelayMs) && options.interPassDelayMs > 0 ? options.interPassDelayMs : 5000;
4148
+ const maxPasses = typeof options.maxPasses === "number" && Number.isFinite(options.maxPasses) && options.maxPasses > 0 ? Math.floor(options.maxPasses) : 3;
4147
4149
  const readinessCheckLabel = options.readinessCheckLabel ?? "Real-call profile history";
4148
4150
  const fetchImpl = options.fetch ?? fetch;
4149
4151
  const recoveryActionsHref = options.recoveryActionsHref ?? "/api/production-readiness/recovery-actions";
@@ -4185,70 +4187,88 @@ var runVoiceRealCallProfileRecoveryLoop = async (options) => {
4185
4187
  actionCount: 0,
4186
4188
  actions,
4187
4189
  jobs: [],
4190
+ passes: 0,
4188
4191
  ok: realCallProfileGate2?.status === "pass",
4189
4192
  realCallProfileGate: realCallProfileGate2,
4190
4193
  startFailures: []
4191
4194
  };
4192
4195
  }
4193
- options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel.`);
4194
- for (const action of actions) {
4195
- options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
4196
- }
4197
- const starts = await Promise.allSettled(actions.map(async (action) => {
4198
- if (!action.href) {
4199
- throw new Error("Recovery action is missing href.");
4200
- }
4201
- const body = await fetchJson(action.href, { method: "POST" });
4202
- return { action, ...body };
4203
- }));
4204
- const startedJobs = starts.flatMap((result) => {
4205
- if (result.status === "rejected") {
4206
- return [];
4207
- }
4208
- return result.value.jobId ? [result.value] : [];
4209
- });
4210
- const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
4211
- {
4212
- action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
4213
- error: result.reason instanceof Error ? result.reason.message : String(result.reason)
4214
- }
4215
- ] : []);
4216
- const pollJob = async (jobId) => {
4217
- const deadline = Date.now() + jobTimeoutMs;
4218
- while (Date.now() < deadline) {
4219
- const body = await fetchJson(resolveJobHref(jobId));
4220
- const job = body.job;
4221
- if (!job) {
4222
- throw new Error(`Recovery job ${jobId} was not found.`);
4196
+ const allJobs = [];
4197
+ const allStartFailures = [];
4198
+ let realCallProfileGate = null;
4199
+ let passes = 0;
4200
+ for (let pass = 1;pass <= maxPasses; pass += 1) {
4201
+ passes = pass;
4202
+ options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel (pass ${String(pass)}/${String(maxPasses)}).`);
4203
+ for (const action of actions) {
4204
+ options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
4205
+ }
4206
+ const starts = await Promise.allSettled(actions.map(async (action) => {
4207
+ if (!action.href) {
4208
+ throw new Error("Recovery action is missing href.");
4209
+ }
4210
+ const body = await fetchJson(action.href, { method: "POST" });
4211
+ return { action, ...body };
4212
+ }));
4213
+ const startedJobs = starts.flatMap((result) => {
4214
+ if (result.status === "rejected") {
4215
+ return [];
4216
+ }
4217
+ return result.value.jobId ? [result.value] : [];
4218
+ });
4219
+ const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
4220
+ {
4221
+ action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
4222
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason)
4223
+ }
4224
+ ] : []);
4225
+ allStartFailures.push(...startFailures);
4226
+ const pollJob = async (jobId) => {
4227
+ const deadline = Date.now() + jobTimeoutMs;
4228
+ while (Date.now() < deadline) {
4229
+ const body = await fetchJson(resolveJobHref(jobId));
4230
+ const job = body.job;
4231
+ if (!job) {
4232
+ throw new Error(`Recovery job ${jobId} was not found.`);
4233
+ }
4234
+ if (job.status === "pass" || job.status === "fail") {
4235
+ return job;
4236
+ }
4237
+ await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
4223
4238
  }
4224
- if (job.status === "pass" || job.status === "fail") {
4225
- return job;
4239
+ throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
4240
+ };
4241
+ options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
4242
+ const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
4243
+ const jobs = jobResults.map((result, index) => ({
4244
+ action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
4245
+ jobId: startedJobs[index]?.jobId,
4246
+ result: result.status === "fulfilled" ? result.value : {
4247
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason),
4248
+ status: "fail"
4226
4249
  }
4227
- await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
4250
+ }));
4251
+ allJobs.push(...jobs);
4252
+ if (refreshHref !== false) {
4253
+ await fetchJson(refreshHref, { method: "POST" });
4228
4254
  }
4229
- throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
4230
- };
4231
- options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
4232
- const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
4233
- const jobs = jobResults.map((result, index) => ({
4234
- action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
4235
- jobId: startedJobs[index]?.jobId,
4236
- result: result.status === "fulfilled" ? result.value : {
4237
- error: result.reason instanceof Error ? result.reason.message : String(result.reason),
4238
- status: "fail"
4255
+ realCallProfileGate = await getGate(true);
4256
+ if (realCallProfileGate?.status === "pass") {
4257
+ break;
4258
+ }
4259
+ if (pass < maxPasses) {
4260
+ options.logger?.log(`Waiting ${String(interPassDelayMs)}ms before the next real-call profile recovery pass.`);
4261
+ await sleepVoiceRealCallProfileRecoveryLoop(interPassDelayMs);
4239
4262
  }
4240
- }));
4241
- if (refreshHref !== false) {
4242
- await fetchJson(refreshHref, { method: "POST" });
4243
4263
  }
4244
- const realCallProfileGate = await getGate(true);
4245
4264
  return {
4246
- actionCount: actions.length,
4265
+ actionCount: actions.length * passes,
4247
4266
  actions,
4248
- jobs,
4249
- ok: startFailures.length === 0 && jobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
4267
+ jobs: allJobs,
4268
+ passes,
4269
+ ok: allStartFailures.length === 0 && allJobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
4250
4270
  realCallProfileGate,
4251
- startFailures
4271
+ startFailures: allStartFailures
4252
4272
  };
4253
4273
  };
4254
4274
  var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
package/dist/vue/index.js CHANGED
@@ -4065,6 +4065,8 @@ var runVoiceRealCallProfileRecoveryLoop = async (options) => {
4065
4065
  const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
4066
4066
  const jobPollMs = options.jobPollMs ?? 1200;
4067
4067
  const jobTimeoutMs = options.jobTimeoutMs ?? 600000;
4068
+ const interPassDelayMs = typeof options.interPassDelayMs === "number" && Number.isFinite(options.interPassDelayMs) && options.interPassDelayMs > 0 ? options.interPassDelayMs : 5000;
4069
+ const maxPasses = typeof options.maxPasses === "number" && Number.isFinite(options.maxPasses) && options.maxPasses > 0 ? Math.floor(options.maxPasses) : 3;
4068
4070
  const readinessCheckLabel = options.readinessCheckLabel ?? "Real-call profile history";
4069
4071
  const fetchImpl = options.fetch ?? fetch;
4070
4072
  const recoveryActionsHref = options.recoveryActionsHref ?? "/api/production-readiness/recovery-actions";
@@ -4106,70 +4108,88 @@ var runVoiceRealCallProfileRecoveryLoop = async (options) => {
4106
4108
  actionCount: 0,
4107
4109
  actions,
4108
4110
  jobs: [],
4111
+ passes: 0,
4109
4112
  ok: realCallProfileGate2?.status === "pass",
4110
4113
  realCallProfileGate: realCallProfileGate2,
4111
4114
  startFailures: []
4112
4115
  };
4113
4116
  }
4114
- options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel.`);
4115
- for (const action of actions) {
4116
- options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
4117
- }
4118
- const starts = await Promise.allSettled(actions.map(async (action) => {
4119
- if (!action.href) {
4120
- throw new Error("Recovery action is missing href.");
4121
- }
4122
- const body = await fetchJson(action.href, { method: "POST" });
4123
- return { action, ...body };
4124
- }));
4125
- const startedJobs = starts.flatMap((result) => {
4126
- if (result.status === "rejected") {
4127
- return [];
4128
- }
4129
- return result.value.jobId ? [result.value] : [];
4130
- });
4131
- const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
4132
- {
4133
- action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
4134
- error: result.reason instanceof Error ? result.reason.message : String(result.reason)
4135
- }
4136
- ] : []);
4137
- const pollJob = async (jobId) => {
4138
- const deadline = Date.now() + jobTimeoutMs;
4139
- while (Date.now() < deadline) {
4140
- const body = await fetchJson(resolveJobHref(jobId));
4141
- const job = body.job;
4142
- if (!job) {
4143
- throw new Error(`Recovery job ${jobId} was not found.`);
4117
+ const allJobs = [];
4118
+ const allStartFailures = [];
4119
+ let realCallProfileGate = null;
4120
+ let passes = 0;
4121
+ for (let pass = 1;pass <= maxPasses; pass += 1) {
4122
+ passes = pass;
4123
+ options.logger?.log(`Running ${String(actions.length)} real-call profile recovery action(s) in parallel (pass ${String(pass)}/${String(maxPasses)}).`);
4124
+ for (const action of actions) {
4125
+ options.logger?.log(`- ${describeVoiceRealCallProfileRecoveryLoopAction(action)}`);
4126
+ }
4127
+ const starts = await Promise.allSettled(actions.map(async (action) => {
4128
+ if (!action.href) {
4129
+ throw new Error("Recovery action is missing href.");
4130
+ }
4131
+ const body = await fetchJson(action.href, { method: "POST" });
4132
+ return { action, ...body };
4133
+ }));
4134
+ const startedJobs = starts.flatMap((result) => {
4135
+ if (result.status === "rejected") {
4136
+ return [];
4137
+ }
4138
+ return result.value.jobId ? [result.value] : [];
4139
+ });
4140
+ const startFailures = starts.flatMap((result, index) => result.status === "rejected" ? [
4141
+ {
4142
+ action: describeVoiceRealCallProfileRecoveryLoopAction(actions[index] ?? {}),
4143
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason)
4144
+ }
4145
+ ] : []);
4146
+ allStartFailures.push(...startFailures);
4147
+ const pollJob = async (jobId) => {
4148
+ const deadline = Date.now() + jobTimeoutMs;
4149
+ while (Date.now() < deadline) {
4150
+ const body = await fetchJson(resolveJobHref(jobId));
4151
+ const job = body.job;
4152
+ if (!job) {
4153
+ throw new Error(`Recovery job ${jobId} was not found.`);
4154
+ }
4155
+ if (job.status === "pass" || job.status === "fail") {
4156
+ return job;
4157
+ }
4158
+ await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
4144
4159
  }
4145
- if (job.status === "pass" || job.status === "fail") {
4146
- return job;
4160
+ throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
4161
+ };
4162
+ options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
4163
+ const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
4164
+ const jobs = jobResults.map((result, index) => ({
4165
+ action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
4166
+ jobId: startedJobs[index]?.jobId,
4167
+ result: result.status === "fulfilled" ? result.value : {
4168
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason),
4169
+ status: "fail"
4147
4170
  }
4148
- await sleepVoiceRealCallProfileRecoveryLoop(jobPollMs);
4171
+ }));
4172
+ allJobs.push(...jobs);
4173
+ if (refreshHref !== false) {
4174
+ await fetchJson(refreshHref, { method: "POST" });
4149
4175
  }
4150
- throw new Error(`Timed out waiting ${String(jobTimeoutMs)}ms for recovery job ${jobId}.`);
4151
- };
4152
- options.logger?.log(`Polling ${String(startedJobs.length)} recovery job(s) in parallel.`);
4153
- const jobResults = await Promise.allSettled(startedJobs.map((start) => pollJob(start.jobId)));
4154
- const jobs = jobResults.map((result, index) => ({
4155
- action: describeVoiceRealCallProfileRecoveryLoopAction(startedJobs[index]?.action ?? {}),
4156
- jobId: startedJobs[index]?.jobId,
4157
- result: result.status === "fulfilled" ? result.value : {
4158
- error: result.reason instanceof Error ? result.reason.message : String(result.reason),
4159
- status: "fail"
4176
+ realCallProfileGate = await getGate(true);
4177
+ if (realCallProfileGate?.status === "pass") {
4178
+ break;
4179
+ }
4180
+ if (pass < maxPasses) {
4181
+ options.logger?.log(`Waiting ${String(interPassDelayMs)}ms before the next real-call profile recovery pass.`);
4182
+ await sleepVoiceRealCallProfileRecoveryLoop(interPassDelayMs);
4160
4183
  }
4161
- }));
4162
- if (refreshHref !== false) {
4163
- await fetchJson(refreshHref, { method: "POST" });
4164
4184
  }
4165
- const realCallProfileGate = await getGate(true);
4166
4185
  return {
4167
- actionCount: actions.length,
4186
+ actionCount: actions.length * passes,
4168
4187
  actions,
4169
- jobs,
4170
- ok: startFailures.length === 0 && jobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
4188
+ jobs: allJobs,
4189
+ passes,
4190
+ ok: allStartFailures.length === 0 && allJobs.every((job) => job.result.status === "pass") && realCallProfileGate?.status === "pass",
4171
4191
  realCallProfileGate,
4172
- startFailures
4192
+ startFailures: allStartFailures
4173
4193
  };
4174
4194
  };
4175
4195
  var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.387",
3
+ "version": "0.0.22-beta.389",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",