impel-cli 0.20.17 → 0.20.18

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/RELEASE_NOTES.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Release notes
2
2
 
3
+ ## 0.20.18 — Recoverable Codex profiling cohorts
4
+
5
+ - Widens the bounded retry window for transient current-tenant reads while
6
+ still rejecting an exact tenant mismatch before a Codex attempt starts.
7
+ - Stops scheduling new work after an attempt-infrastructure failure, waits for
8
+ active workers to settle, and retains an explicitly incomplete private
9
+ aggregate so valid samples are not silently lost.
10
+
3
11
  ## 0.20.17 — Managed desktop task views
4
12
 
5
13
  - Enables the reviewed MCP Apps renderer only in exact managed Codex Desktop
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "impel-cli",
3
- "version": "0.20.17",
3
+ "version": "0.20.18",
4
4
  "description": "Prepare isolated Claude and Codex workspaces for every accessible Impel tenant",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,7 @@ const PROFILE_SCHEMA = "impel.native-codex-profile.v1";
14
14
  const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
15
15
  const DEFAULT_GRACE_MS = 5_000;
16
16
  const MAX_CAPTURE_BYTES = 256 * 1024 * 1024;
17
+ const TENANT_PREFLIGHT_RETRY_DELAYS_MS = [250, 750, 1_500];
17
18
 
18
19
  function privateDirectory(directory) {
19
20
  if (fs.existsSync(directory) && fs.lstatSync(directory).isSymbolicLink()) {
@@ -211,16 +212,17 @@ function runCaptured(command, args, { environment = process.env, timeoutMs = 60_
211
212
  }
212
213
 
213
214
  async function currentTenant(impelBinary, environment) {
214
- for (let attempt = 0; attempt < 2; attempt += 1) {
215
+ for (let attempt = 0; attempt <= TENANT_PREFLIGHT_RETRY_DELAYS_MS.length; attempt += 1) {
215
216
  const invocation = impelInvocation(impelBinary, ["tenant", "current"], environment);
216
217
  const result = await runCaptured(invocation.command, invocation.args, { environment });
217
218
  const lines = result.stdout.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
218
219
  if (result.code === 0 && lines.length === 1 && /^[A-Za-z0-9_.:-]{1,160}$/u.test(lines[0])) {
219
220
  return lines[0];
220
221
  }
221
- if (attempt === 0) await delay(250);
222
+ const retryDelay = TENANT_PREFLIGHT_RETRY_DELAYS_MS[attempt];
223
+ if (retryDelay !== undefined) await delay(retryDelay);
222
224
  }
223
- throw new Error("could not establish one exact current Impel tenant after two bounded attempts");
225
+ throw new Error("could not establish one exact current Impel tenant after bounded attempts");
224
226
  }
225
227
 
226
228
  export async function assertExpectedTenant({ expectedTenant, impelBinary = "impel", environment = process.env }) {
@@ -495,19 +497,38 @@ async function profileAttempt(options, sessionDir, index) {
495
497
  return record;
496
498
  }
497
499
 
498
- async function runWorkers(options, sessionDir) {
500
+ function profilerFailureClass(error) {
501
+ if (/could not establish one exact current Impel tenant/u.test(error?.message || "")) {
502
+ return "tenant-preflight-unavailable";
503
+ }
504
+ if (/selected tenant .* does not match expected tenant/u.test(error?.message || "")) {
505
+ return "tenant-mismatch";
506
+ }
507
+ return "attempt-infrastructure";
508
+ }
509
+
510
+ export async function runWorkers(options, sessionDir, profileAttemptImpl = profileAttempt) {
499
511
  const results = new Array(options.attempts);
500
512
  let next = 0;
513
+ let abort = null;
501
514
  const worker = async () => {
502
515
  for (;;) {
516
+ if (abort) return;
503
517
  const index = next;
504
518
  next += 1;
505
519
  if (index >= options.attempts) return;
506
- results[index] = await profileAttempt(options, sessionDir, index);
520
+ try {
521
+ results[index] = await profileAttemptImpl(options, sessionDir, index);
522
+ } catch (error) {
523
+ abort ||= {
524
+ failureClass: profilerFailureClass(error),
525
+ attemptId: `${options.cohort}-${String(index + 1 + options.attemptOffset).padStart(3, "0")}`,
526
+ };
527
+ }
507
528
  }
508
529
  };
509
530
  await Promise.all(Array.from({ length: options.concurrency }, () => worker()));
510
- return results;
531
+ return { attempts: results.filter(Boolean), abort };
511
532
  }
512
533
 
513
534
  async function restoreTenant(originalTenant, options) {
@@ -553,7 +574,7 @@ async function main(argv) {
553
574
  privateDirectory(sessionDir);
554
575
  let restored = false;
555
576
  try {
556
- const attempts = await runWorkers(options, sessionDir);
577
+ const result = await runWorkers(options, sessionDir);
557
578
  const aggregate = {
558
579
  schema: PROFILE_SCHEMA,
559
580
  sessionId,
@@ -564,12 +585,18 @@ async function main(argv) {
564
585
  cohort: options.cohort,
565
586
  cliVersion: options.hostBuild,
566
587
  promptSha256: options.promptSha256,
567
- attempts,
588
+ complete: result.abort === null && result.attempts.length === options.attempts,
589
+ expectedAttempts: options.attempts,
590
+ abort: result.abort,
591
+ attempts: result.attempts,
568
592
  };
569
593
  const aggregatePath = path.join(sessionDir, "aggregate.json");
570
594
  privateWrite(aggregatePath, `${JSON.stringify(aggregate, null, 2)}\n`);
571
595
  process.stderr.write(`Private Codex profile written under ${sessionDir}\n`);
572
596
  process.stdout.write(`${JSON.stringify(aggregate)}\n`);
597
+ if (!aggregate.complete) {
598
+ throw new Error(`cohort aborted (${aggregate.abort?.failureClass || "incomplete"}); partial aggregate retained`);
599
+ }
573
600
  } finally {
574
601
  restored = await restoreTenant(originalTenant, options);
575
602
  if (!restored) process.stderr.write("profile-native-codex: could not verify restoration of the original tenant\n");