@timurproko/a1 0.2.7-dev.1255 → 0.2.7-dev.1264

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.
Files changed (78) hide show
  1. package/dist/app/session-shell/prompt-submission.d.ts +1 -1
  2. package/dist/app/session-shell/prompt-submission.js +9 -9
  3. package/dist/app/session-shell/resource-labels.d.ts +2 -0
  4. package/dist/app/session-shell/resource-labels.js +158 -0
  5. package/dist/app/session-shell/session-prompt-history.d.ts +29 -0
  6. package/dist/app/session-shell/session-prompt-history.js +80 -0
  7. package/dist/app/session-shell/session-selectors.d.ts +46 -0
  8. package/dist/app/session-shell/session-selectors.js +269 -0
  9. package/dist/app/session-shell/session-shell-root.d.ts +6 -28
  10. package/dist/app/session-shell/session-shell-root.js +26 -220
  11. package/dist/app/session-shell/session-shell.d.ts +3 -23
  12. package/dist/app/session-shell/session-shell.js +54 -365
  13. package/dist/app/session-shell/session-viewport-controller.d.ts +2 -10
  14. package/dist/app/session-shell/session-viewport-controller.js +40 -318
  15. package/dist/app/session-shell/settings-bindings.d.ts +1 -1
  16. package/dist/app/session-shell/settings-bindings.js +2 -2
  17. package/dist/app/session-shell/viewport-pointer.d.ts +83 -0
  18. package/dist/app/session-shell/viewport-pointer.js +361 -0
  19. package/dist/composition/owned-ui.js +63 -59
  20. package/dist/contracts/owned-ui/index.d.ts +1 -1
  21. package/dist/contracts/owned-ui/index.js +1 -1
  22. package/dist/contracts/owned-ui/validation.d.ts +1 -11
  23. package/dist/contracts/owned-ui/validation.js +8 -104
  24. package/dist/contracts/presentation/index.d.ts +1 -1
  25. package/dist/contracts/presentation/terminal.d.ts +0 -6
  26. package/dist/features/owned-ui/settings-app.js +13 -208
  27. package/dist/features/owned-ui/settings-model-dialog.d.ts +41 -0
  28. package/dist/features/owned-ui/settings-model-dialog.js +194 -0
  29. package/dist/foundation/release/automatic-update.js +1 -8
  30. package/dist/foundation/release/dependency-layer.js +3 -3
  31. package/dist/foundation/release/latest-release.js +1 -1
  32. package/dist/foundation/release/managed-path.d.ts +10 -0
  33. package/dist/foundation/release/managed-path.js +26 -0
  34. package/dist/foundation/release/release-gc.js +6 -8
  35. package/dist/foundation/release/restart-certification.js +3 -4
  36. package/dist/foundation/release/update-activation.js +1 -1
  37. package/dist/foundation/release/update-execution-lease.js +1 -7
  38. package/dist/foundation/release/update-installer.d.ts +45 -0
  39. package/dist/foundation/release/update-installer.js +57 -0
  40. package/dist/foundation/release/update-recovery.js +2 -9
  41. package/dist/foundation/release/update.d.ts +4 -23
  42. package/dist/foundation/release/update.js +160 -179
  43. package/dist/integrations/pi/components/bracketed-paste.d.ts +33 -0
  44. package/dist/integrations/pi/components/bracketed-paste.js +123 -0
  45. package/dist/integrations/pi/components/owned-editor-ux.js +46 -140
  46. package/dist/integrations/pi/engine/adapter.d.ts +1 -8
  47. package/dist/integrations/pi/engine/adapter.js +6 -49
  48. package/dist/integrations/pi/engine/host.js +1 -1
  49. package/dist/integrations/pi/engine/repository-metadata.d.ts +49 -0
  50. package/dist/integrations/pi/engine/repository-metadata.js +189 -0
  51. package/dist/integrations/pi/engine/session-runtime.d.ts +6 -12
  52. package/dist/integrations/pi/engine/session-runtime.js +21 -178
  53. package/dist/integrations/pi/engine/view-state.d.ts +21 -2
  54. package/dist/integrations/pi/engine/view-state.js +129 -1
  55. package/dist/integrations/pi/engine/workflow-runner.d.ts +0 -1
  56. package/dist/integrations/pi/engine/workflow-runner.js +254 -365
  57. package/dist/integrations/pi/engine/workflow-share.d.ts +9 -0
  58. package/dist/integrations/pi/engine/workflow-share.js +168 -0
  59. package/dist/integrations/pi/engine/workflow-support.d.ts +0 -14
  60. package/dist/integrations/pi/engine/workflow-support.js +0 -47
  61. package/dist/integrations/pi/tui-runtime/adapter.d.ts +9 -19
  62. package/dist/integrations/pi/tui-runtime/adapter.js +35 -144
  63. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +4 -4
  64. package/dist/integrations/pi/tui-runtime/index.d.ts +0 -1
  65. package/dist/integrations/pi/tui-runtime/overlay-geometry.d.ts +2 -2
  66. package/dist/integrations/pi/tui-runtime/presentation-adapter.d.ts +2 -2
  67. package/dist/ui/components/transcript-viewport-frame.d.ts +120 -0
  68. package/dist/ui/components/transcript-viewport-frame.js +314 -0
  69. package/dist/ui/components/transcript-viewport-types.d.ts +147 -0
  70. package/dist/ui/components/transcript-viewport.d.ts +2 -147
  71. package/dist/ui/components/transcript-viewport.js +87 -315
  72. package/package.json +3 -3
  73. package/dist/integrations/pi/engine/diagnostics-state.d.ts +0 -9
  74. package/dist/integrations/pi/engine/diagnostics-state.js +0 -34
  75. package/dist/integrations/pi/engine/usage-view.d.ts +0 -4
  76. package/dist/integrations/pi/engine/usage-view.js +0 -56
  77. package/dist/integrations/pi/tui-runtime/contracts.d.ts +0 -22
  78. /package/dist/{integrations/pi/tui-runtime/contracts.js → ui/components/transcript-viewport-types.js} +0 -0
@@ -7,15 +7,14 @@ import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
7
7
  import { earlyAccentPalette, earlyTerminalAppearance, earlyTerminalColorMode, resolveEarlyAccentColor } from "./early-accent.js";
8
8
  import { SHARED_PROGRESS_ACCENT_ANSI } from "../../contracts/presentation/index.js";
9
9
  import { createRegistryFetcher, invalidateReleaseCheckCache, isNewerRelease, queryPublishedVersions, releaseChannelOf, } from "./latest-release.js";
10
- import { certifyMaterializedRelease } from "./bootstrap.js";
11
10
  import { resolveProductPaths } from "../lifecycle/index.js";
12
11
  import { ReleaseStateStore } from "./release-state.js";
13
- import { materializeRelease, readMaterializedRelease } from "./release-store.js";
14
- import { scheduleReleaseCleanup } from "./release-gc.js";
15
12
  import { UPDATE_ACTIVATION_CONTRACT, activateInstalledRelease, delegateActivation, readActivationContracts, } from "./update-activation.js";
16
13
  import { UpdateTransactionStore } from "./update-transaction.js";
17
14
  import { acquireUpdateExecutionLease, reclaimDeadLease } from "./update-execution-lease.js";
18
- import { npmPrefixForGlobalRoot, removeUpdateRecoveryCapsule, resolveNpmCli, runProtectedPackageReplacement, updateLauncherPaths, updateNpmInstallArguments, } from "./update-recovery.js";
15
+ import { npmPrefixForGlobalRoot, removeUpdateRecoveryCapsule, updateLauncherPaths, } from "./update-recovery.js";
16
+ import { errorMessage, samePath } from "./managed-path.js";
17
+ import { createProtectedUpdateInstaller, } from "./update-installer.js";
19
18
  export const PRODUCT_PACKAGE = PRODUCT_TEXT.packageName;
20
19
  const UPDATE_DIST_TAGS = { stable: "latest", next: "next" };
21
20
  const defaultFileSystem = {
@@ -410,14 +409,14 @@ function resolveExactAutomaticTarget(target, channel, output) {
410
409
  function reportUnmanagedInstallation(output, packageRoot, activeGlobalRoot) {
411
410
  output.stderr(`${PRODUCT_TEXT.diagnostic(`refused to update automatically because ${packageRoot} could not be verified as an npm-managed ${PRODUCT_PACKAGE} installation; npm's active global package root is ${activeGlobalRoot}. Run npm install --global ${PRODUCT_PACKAGE} to install it manually.`)}\n`);
412
411
  }
413
- async function withUpdateExecutionLease(input, options, output, operation) {
412
+ async function withUpdateExecutionLease(input, options, output, installer, operation) {
414
413
  let lease = await acquireUpdateExecutionLease(input);
415
414
  if (lease.status === "interrupted") {
416
415
  // Rationale: the owner that targeted another version is gone. Its durable transaction is rolled
417
416
  // back to the release that ran before it, so this request proceeds as an ordinary update of its
418
417
  // own target instead of reporting a dead attempt as in progress.
419
418
  const interrupted = lease.owner.targetVersion;
420
- const rollback = await rollbackPriorRelease(input.dataDir, lease.transaction.priorActiveReleaseId)
419
+ const rollback = await installer.rollback(lease.transaction.priorActiveReleaseId)
421
420
  .catch(error => `rollback failed: ${errorMessage(error)}`);
422
421
  const store = new UpdateTransactionStore(input.dataDir);
423
422
  await store.finish("rolled-back", `interrupted ${interrupted} update settled by ${input.targetVersion}; ${rollback}`);
@@ -457,6 +456,19 @@ async function readRunningPackageVersion(packageRoot, fileSystem, output) {
457
456
  }
458
457
  }
459
458
  export async function runSelfUpdate(options) {
459
+ const plan = await planSelfUpdate(options);
460
+ if (typeof plan === "number")
461
+ return plan;
462
+ return await withUpdateExecutionLease({
463
+ dataDir: plan.paths.dataDir,
464
+ packageRoot: plan.installation.packageRoot,
465
+ channel: plan.channel,
466
+ targetVersion: plan.targetVersion,
467
+ mode: plan.mode,
468
+ }, options, plan.output, plan.installer, async () => await runUpdateTransaction(plan));
469
+ }
470
+ /** Resolve the running version, the target and the managed installation, or the exit code that ends the update. */
471
+ async function planSelfUpdate(options) {
460
472
  const fileSystem = options.fileSystem ?? defaultFileSystem;
461
473
  const output = options.output ?? defaultOutput;
462
474
  const runner = options.runner ?? createNpmProcessRunner();
@@ -499,164 +511,155 @@ export async function runSelfUpdate(options) {
499
511
  const managed = await measure("global-root", async () => await resolveManagedNpmInstallation(options.packageRoot, fileSystem, runner, output));
500
512
  if (managed.installation === null)
501
513
  return managed.exitCode;
502
- const { packageRoot, globalRoot, npmCliRoot } = managed.installation;
503
514
  const paths = resolveProductPaths(environment);
504
- const lifecycle = options.lifecycle ?? createUpdateLifecycleCoordinator(environment, fileSystem, output);
505
- const transactionStore = options.transactionStore ?? new UpdateTransactionStore(paths.dataDir);
506
- const mode = options.mode ?? "manual";
507
- const maintenance = options.maintenance ?? (options.lifecycle
508
- ? async () => { }
509
- : async () => await scheduleReleaseCleanup(paths.dataDir, paths));
510
- return await withUpdateExecutionLease({ dataDir: paths.dataDir, packageRoot, channel, targetVersion, mode }, options, output, async () => {
511
- let transaction = await transactionStore.read();
515
+ return {
516
+ output,
517
+ channel,
518
+ targetVersion,
519
+ progress,
520
+ installation: managed.installation,
521
+ paths,
522
+ lifecycle: options.lifecycle ?? createUpdateLifecycleCoordinator(environment, fileSystem, output),
523
+ transactionStore: options.transactionStore ?? new UpdateTransactionStore(paths.dataDir),
524
+ installer: options.installer ?? createProtectedUpdateInstaller(paths, environment),
525
+ mode: options.mode ?? "manual",
526
+ measure,
527
+ now,
528
+ onPhaseTiming: options.onPhaseTiming,
529
+ };
530
+ }
531
+ async function runUpdateTransaction(plan) {
532
+ const { output, progress, installer, lifecycle, transactionStore, targetVersion } = plan;
533
+ const { packageRoot, npmCliRoot } = plan.installation;
534
+ const run = { transaction: await transactionStore.read() };
535
+ try {
536
+ if ((!run.transaction || run.transaction.status === "completed") && await lifecycle.targetIsActive(targetVersion)) {
537
+ await installer.maintenance();
538
+ await transactionStore.clearCompleted();
539
+ output.stdout(`${PRODUCT_TEXT.commandName} is up to date — no update needed.\n`);
540
+ return 0;
541
+ }
512
542
  let npmCli;
513
543
  try {
514
- if ((!transaction || transaction.status === "completed") && await lifecycle.targetIsActive(targetVersion)) {
515
- await maintenance();
516
- await transactionStore.clearCompleted();
517
- output.stdout(`${PRODUCT_TEXT.commandName} is up to date — no update needed.\n`);
518
- return 0;
519
- }
520
- const preflightRequired = options.npmCliResolver !== undefined
521
- || options.packageReplacement === undefined && options.runner === undefined;
522
- if (preflightRequired) {
523
- try {
524
- npmCli = await (options.npmCliResolver ?? resolveNpmCli)(npmCliRoot, environment);
525
- }
526
- catch (error) {
527
- throw new UpdatePreflightFailure(errorMessage(error));
528
- }
529
- }
530
- // Rationale: the bar first appears only after every non-mutating replacement prerequisite passes.
531
- progress.set(3, 15);
532
- const releaseStateStore = new ReleaseStateStore(paths.dataDir);
533
- let currentReleaseState = await releaseStateStore.read();
534
- let priorActiveReleaseId = currentReleaseState.references.active;
535
- if (options.runner === undefined && (priorActiveReleaseId === null || currentReleaseState.releases[priorActiveReleaseId]?.approval !== "approved")) {
536
- // Invariant: protected replacement needs an immutable launch authority that npm cannot rename.
537
- const recoveryRelease = await materializeRelease(packageRoot, paths.dataDir);
538
- await releaseStateStore.recordCandidate(recoveryRelease);
539
- const diagnostics = await certifyMaterializedRelease(recoveryRelease, paths.dataDir);
540
- await releaseStateStore.approve(recoveryRelease.releaseId, diagnostics);
541
- await releaseStateStore.activate(recoveryRelease.releaseId);
542
- currentReleaseState = await releaseStateStore.read();
543
- priorActiveReleaseId = recoveryRelease.releaseId;
544
- }
545
- transaction = await transactionStore.begin({
546
- channel,
547
- targetVersion,
548
- packageRoot,
549
- priorActiveReleaseId,
550
- });
551
- if (phaseBefore(transaction.phase, "ownership-released")) {
552
- await measure("ownership-release", async () => { await lifecycle.verifyPackageUnlocked(packageRoot); });
553
- transaction = await transactionStore.advance("ownership-released");
554
- }
555
- progress.set(15, 70);
556
- if (phaseBefore(transaction.phase, "package-installed")) {
557
- if (options.packageReplacement || options.runner === undefined) {
558
- const priorReleaseId = transaction.priorActiveReleaseId ?? priorActiveReleaseId;
559
- const recordedPrior = priorReleaseId === null ? undefined : currentReleaseState.releases[priorReleaseId];
560
- if ((!recordedPrior || recordedPrior.approval !== "approved") && !options.packageReplacement) {
561
- throw new Error(PRODUCT_TEXT.diagnostic("cannot protect package replacement because no approved prior release is available"));
562
- }
563
- const priorRelease = recordedPrior ?? {
564
- releaseId: priorReleaseId ?? "injected-prior-release",
565
- releaseRoot: packageRoot,
566
- contentDigest: "0".repeat(64),
567
- };
568
- const replacementTransaction = transaction;
569
- const replacement = await measure("npm-install", async () => await (options.packageReplacement ?? runProtectedPackageReplacement)({
570
- dataDir: paths.dataDir,
571
- globalRoot,
572
- npmCliRoot,
573
- ...(npmCli === undefined ? {} : { npmCli }),
574
- packageRoot,
575
- transaction: replacementTransaction,
576
- priorRelease,
577
- output,
578
- environment,
579
- onRecoveryState: async (recovery) => {
580
- if (transactionStore.setRecovery)
581
- transaction = await transactionStore.setRecovery(recovery) ?? transaction;
582
- },
583
- }));
584
- // Rationale: npm's own text explains a failed replacement and says nothing about a
585
- // successful one, so it reaches the terminal only when the installation did not happen.
586
- if (replacement.outcome !== "installed")
587
- reportChildDiagnostics(output, replacement);
588
- if (replacement.outcome === "installed")
589
- transaction = await transactionStore.advance("package-installed");
590
- if (replacement.cancelled) {
591
- progress.clear();
592
- output.stderr(`${PRODUCT_TEXT.diagnostic("update cancelled safely; the launcher is available and the transaction can be resumed.")}\n`);
593
- return 130;
594
- }
595
- if (replacement.outcome !== "installed") {
596
- throw new UpdateFailure(unsuccessfulCode(replacement.npmExitCode), `npm exited with status ${formatExitCode(replacement.npmExitCode)}`);
597
- }
598
- }
599
- else {
600
- const installation = await measure("npm-install", async () => await runNpm(runner, updateNpmInstallArguments(globalRoot, targetVersion), true, output, "start the global npm installation", false));
601
- if (installation.result === null)
602
- throw new UpdateFailure(installation.exitCode, "npm process failed");
603
- if (installation.result.code !== 0) {
604
- reportChildDiagnostics(output, installation.result);
605
- throw new UpdateFailure(unsuccessfulCode(installation.result.code), `npm exited with status ${formatExitCode(installation.result.code)}`);
606
- }
607
- transaction = await transactionStore.advance("package-installed");
608
- }
609
- }
610
- progress.set(70, MATERIALIZE_PROGRESS.from);
611
- let activationPhaseStartedAt = now();
612
- const recordActivationPhase = async (phase) => {
613
- options.onPhaseTiming?.({ phase, durationMs: Math.max(0, now() - activationPhaseStartedAt) });
614
- transaction = await transactionStore.advance(phase);
615
- progress.set(ACTIVATION_PROGRESS[phase].at, ACTIVATION_PROGRESS[phase].creepTo);
616
- activationPhaseStartedAt = now();
617
- };
618
- await lifecycle.activateInstalled(packageRoot, targetVersion, recordActivationPhase, ({ completed, total }) => progress.set(MATERIALIZE_PROGRESS.from
619
- + (MATERIALIZE_PROGRESS.to - MATERIALIZE_PROGRESS.from) * (total > 0 ? Math.min(1, completed / total) : 0)), state => {
620
- if (state === "started") {
621
- activationPhaseStartedAt = now();
622
- progress.set(98, 99);
623
- }
624
- else {
625
- options.onPhaseTiming?.({ phase: "warmup", durationMs: Math.max(0, now() - activationPhaseStartedAt) });
626
- activationPhaseStartedAt = now();
627
- progress.set(99);
628
- }
629
- });
630
- const transactionStartedAt = now();
631
- if (transaction.recovery?.capsulePath)
632
- await removeUpdateRecoveryCapsule(paths.dataDir, transaction.transactionId);
633
- // Invariant: successful output follows the durable cleanup disposition. Slow recursive
634
- // removal belongs to the detached worker started by this maintenance coordinator.
635
- await maintenance();
636
- // Concurrency: this is the launch cutover. No failure-prone update work follows success.
637
- await transactionStore.finish("completed");
638
- await Promise.all([transactionStore.clearCompleted(), invalidateReleaseCheckCache(paths.configDir)]).catch(() => undefined);
639
- options.onPhaseTiming?.({ phase: "transaction-complete", durationMs: Math.max(0, now() - transactionStartedAt) });
640
- progress.finish();
641
- output.stdout(`${PRODUCT_TEXT.commandName} updated successfully to ${targetVersion}\n`);
642
- return 0;
544
+ npmCli = await installer.preflight(npmCliRoot);
643
545
  }
644
546
  catch (error) {
645
- progress.clear();
646
- const message = errorMessage(error);
647
- if (error instanceof UpdatePreflightFailure) {
648
- output.stderr(`${PRODUCT_TEXT.diagnostic(`update could not start safely: ${message}. Nothing was changed. Retry with npm x -y -- ${PRODUCT_PACKAGE}-install`)}\n`);
649
- return 1;
650
- }
651
- const rollback = options.lifecycle
652
- ? "previous test lifecycle retained"
653
- : await rollbackPriorRelease(paths.dataDir, transaction?.priorActiveReleaseId ?? null).catch(rollbackError => `rollback failed: ${errorMessage(rollbackError)}`);
654
- if (transaction)
655
- await transactionStore.finish(rollback === "rolled back" ? "rolled-back" : "failed", `${message}; ${rollback}`);
656
- output.stderr(updateFailureLine(message, rollback, transactionStore.path));
657
- return error instanceof UpdateFailure ? error.exitCode : 1;
547
+ throw new UpdatePreflightFailure(errorMessage(error));
548
+ }
549
+ // Rationale: the bar first appears only after every non-mutating replacement prerequisite passes.
550
+ progress.set(3, 15);
551
+ const protection = await installer.protectPriorRelease(packageRoot);
552
+ run.transaction = await transactionStore.begin({
553
+ channel: plan.channel,
554
+ targetVersion,
555
+ packageRoot,
556
+ priorActiveReleaseId: protection.priorActiveReleaseId,
557
+ });
558
+ if (phaseBefore(run.transaction.phase, "ownership-released")) {
559
+ await plan.measure("ownership-release", async () => { await lifecycle.verifyPackageUnlocked(packageRoot); });
560
+ run.transaction = await transactionStore.advance("ownership-released");
561
+ }
562
+ progress.set(15, 70);
563
+ if (phaseBefore(run.transaction.phase, "package-installed")) {
564
+ const cancelled = await replaceUpdatePackage(plan, run, protection, npmCli);
565
+ if (cancelled !== null)
566
+ return cancelled;
567
+ }
568
+ return await activateUpdate(plan, run);
569
+ }
570
+ catch (error) {
571
+ return await settleUpdateFailure(plan, run.transaction, error);
572
+ }
573
+ }
574
+ /** Replace the package under protection; returns an exit code when the user cancelled, null once installed. */
575
+ async function replaceUpdatePackage(plan, run, protection, npmCli) {
576
+ const { output, progress, transactionStore } = plan;
577
+ const transaction = run.transaction;
578
+ const priorRelease = protection.approvedRelease(transaction.priorActiveReleaseId ?? protection.priorActiveReleaseId);
579
+ if (priorRelease === undefined) {
580
+ throw new Error(PRODUCT_TEXT.diagnostic("cannot protect package replacement because no approved prior release is available"));
581
+ }
582
+ const replacement = await plan.measure("npm-install", async () => await plan.installer.replacePackage({
583
+ globalRoot: plan.installation.globalRoot,
584
+ npmCliRoot: plan.installation.npmCliRoot,
585
+ ...(npmCli === undefined ? {} : { npmCli }),
586
+ packageRoot: plan.installation.packageRoot,
587
+ transaction,
588
+ priorRelease,
589
+ output,
590
+ onRecoveryState: async (recovery) => {
591
+ if (transactionStore.setRecovery)
592
+ run.transaction = await transactionStore.setRecovery(recovery) ?? run.transaction;
593
+ },
594
+ }));
595
+ // Rationale: npm's own text explains a failed replacement and says nothing about a
596
+ // successful one, so it reaches the terminal only when the installation did not happen.
597
+ if (replacement.outcome !== "installed")
598
+ reportChildDiagnostics(output, replacement);
599
+ if (replacement.outcome === "installed")
600
+ run.transaction = await transactionStore.advance("package-installed");
601
+ if (replacement.cancelled) {
602
+ progress.clear();
603
+ output.stderr(`${PRODUCT_TEXT.diagnostic("update cancelled safely; the launcher is available and the transaction can be resumed.")}\n`);
604
+ return 130;
605
+ }
606
+ if (replacement.outcome !== "installed") {
607
+ throw new UpdateFailure(unsuccessfulCode(replacement.npmExitCode), `npm exited with status ${formatExitCode(replacement.npmExitCode)}`);
608
+ }
609
+ return null;
610
+ }
611
+ /** Activate the installed package, commit maintenance, and only then report success. */
612
+ async function activateUpdate(plan, run) {
613
+ const { output, progress, transactionStore, now, onPhaseTiming, paths } = plan;
614
+ progress.set(70, MATERIALIZE_PROGRESS.from);
615
+ let activationPhaseStartedAt = now();
616
+ const recordActivationPhase = async (phase) => {
617
+ onPhaseTiming?.({ phase, durationMs: Math.max(0, now() - activationPhaseStartedAt) });
618
+ run.transaction = await transactionStore.advance(phase);
619
+ progress.set(ACTIVATION_PROGRESS[phase].at, ACTIVATION_PROGRESS[phase].creepTo);
620
+ activationPhaseStartedAt = now();
621
+ };
622
+ await plan.lifecycle.activateInstalled(plan.installation.packageRoot, plan.targetVersion, recordActivationPhase, ({ completed, total }) => progress.set(MATERIALIZE_PROGRESS.from
623
+ + (MATERIALIZE_PROGRESS.to - MATERIALIZE_PROGRESS.from) * (total > 0 ? Math.min(1, completed / total) : 0)), state => {
624
+ if (state === "started") {
625
+ activationPhaseStartedAt = now();
626
+ progress.set(98, 99);
627
+ }
628
+ else {
629
+ onPhaseTiming?.({ phase: "warmup", durationMs: Math.max(0, now() - activationPhaseStartedAt) });
630
+ activationPhaseStartedAt = now();
631
+ progress.set(99);
658
632
  }
659
633
  });
634
+ const transactionStartedAt = now();
635
+ const transaction = run.transaction;
636
+ if (transaction?.recovery?.capsulePath)
637
+ await removeUpdateRecoveryCapsule(paths.dataDir, transaction.transactionId);
638
+ // Invariant: successful output follows the durable cleanup disposition. Slow recursive
639
+ // removal belongs to the detached worker started by this maintenance coordinator.
640
+ await plan.installer.maintenance();
641
+ // Concurrency: this is the launch cutover. No failure-prone update work follows success.
642
+ await transactionStore.finish("completed");
643
+ await Promise.all([transactionStore.clearCompleted(), invalidateReleaseCheckCache(paths.configDir)]).catch(() => undefined);
644
+ onPhaseTiming?.({ phase: "transaction-complete", durationMs: Math.max(0, now() - transactionStartedAt) });
645
+ progress.finish();
646
+ output.stdout(`${PRODUCT_TEXT.commandName} updated successfully to ${plan.targetVersion}\n`);
647
+ return 0;
648
+ }
649
+ async function settleUpdateFailure(plan, transaction, error) {
650
+ const { output, transactionStore } = plan;
651
+ plan.progress.clear();
652
+ const message = errorMessage(error);
653
+ if (error instanceof UpdatePreflightFailure) {
654
+ output.stderr(`${PRODUCT_TEXT.diagnostic(`update could not start safely: ${message}. Nothing was changed. Retry with npm x -y -- ${PRODUCT_PACKAGE}-install`)}\n`);
655
+ return 1;
656
+ }
657
+ const rollback = await plan.installer.rollback(transaction?.priorActiveReleaseId ?? null)
658
+ .catch(rollbackError => `rollback failed: ${errorMessage(rollbackError)}`);
659
+ if (transaction)
660
+ await transactionStore.finish(rollback === "rolled back" ? "rolled-back" : "failed", `${message}; ${rollback}`);
661
+ output.stderr(updateFailureLine(message, rollback, transactionStore.path));
662
+ return error instanceof UpdateFailure ? error.exitCode : 1;
660
663
  }
661
664
  export function assertUpdatePerformanceBudget(evidence, maximumPostNpmDurationMs = 30_000) {
662
665
  const failures = [];
@@ -698,24 +701,6 @@ function reportChildDiagnostics(output, result) {
698
701
  output.stderr(`${text}\n`);
699
702
  return true;
700
703
  }
701
- async function rollbackPriorRelease(dataDir, priorReleaseId) {
702
- if (!priorReleaseId)
703
- return "no prior release was available for rollback";
704
- const stateStore = new ReleaseStateStore(dataDir);
705
- const state = await stateStore.read();
706
- const prior = state.releases[priorReleaseId];
707
- if (!prior || prior.approval !== "approved")
708
- return "prior release is not a verified rollback candidate";
709
- if (state.references.active !== priorReleaseId) {
710
- if (state.references.rollback === priorReleaseId)
711
- await stateStore.rollback();
712
- else
713
- throw new Error(`prior release ${priorReleaseId} is not the recorded rollback release`);
714
- }
715
- // Invariant: rollback selects only a prior release whose retained content still verifies.
716
- await readMaterializedRelease(prior.releaseRoot);
717
- return "rolled back";
718
- }
719
704
  /** One reason and one next step; routine rollback detail stays in the journal. */
720
705
  function updateFailureLine(message, rollback, journalPath) {
721
706
  const note = rollback.startsWith("rollback failed") ? ` (${rollback})` : "";
@@ -731,9 +716,5 @@ class UpdateFailure extends Error {
731
716
  }
732
717
  class UpdatePreflightFailure extends Error {
733
718
  }
734
- function samePath(left, right) {
735
- return process.platform === "win32" ? resolve(left).toLowerCase() === resolve(right).toLowerCase() : resolve(left) === resolve(right);
736
- }
737
719
  function unsuccessfulCode(code) { return code === null || code === 0 ? 1 : code; }
738
720
  function formatExitCode(code) { return code === null ? "unknown" : String(code); }
739
- function errorMessage(error) { return error instanceof Error ? error.message : String(error); }
@@ -0,0 +1,33 @@
1
+ export declare const PASTE_START = "\u001B[200~";
2
+ /** A bracketed paste whose end marker arrived, with the input that followed it in the same chunk. */
3
+ export interface CompletedPaste {
4
+ readonly text: string;
5
+ /** UTF-8 bytes counted against the cap; zero when the framer has no cap. */
6
+ readonly bytes: number;
7
+ /** The paste exceeded the cap, so its text was dropped. */
8
+ readonly oversized: boolean;
9
+ readonly remaining: string;
10
+ }
11
+ /**
12
+ * Bracketed-paste framing across terminal input chunks. `feed` collects a paste for insertion;
13
+ * `discard` drops pastes while forwarding the input around them, and also holds back a split
14
+ * start marker so a paste can never leak in as typed text when paste is disabled.
15
+ */
16
+ export declare class BracketedPasteFramer {
17
+ #private;
18
+ /** With `maxBytes`, a paste beyond it is reported as oversized and its text is dropped. */
19
+ constructor(maxBytes?: number);
20
+ /** A paste has started and its end marker has not arrived. */
21
+ get open(): boolean;
22
+ clear(): void;
23
+ /**
24
+ * Feed input that continues the open paste, or that begins with the start marker. Returns the
25
+ * completed paste, or undefined while the end marker is still to come.
26
+ */
27
+ feed(data: string): CompletedPaste | undefined;
28
+ /**
29
+ * Drop any bracketed paste in `data`, forwarding the input before and after it. False means
30
+ * `data` holds no paste and nothing was forwarded, so the caller handles it as ordinary input.
31
+ */
32
+ discard(data: string, forward: (data: string) => void): boolean;
33
+ }
@@ -0,0 +1,123 @@
1
+ export const PASTE_START = "\x1b[200~";
2
+ const PASTE_END = "\x1b[201~";
3
+ /**
4
+ * Bracketed-paste framing across terminal input chunks. `feed` collects a paste for insertion;
5
+ * `discard` drops pastes while forwarding the input around them, and also holds back a split
6
+ * start marker so a paste can never leak in as typed text when paste is disabled.
7
+ */
8
+ export class BracketedPasteFramer {
9
+ #maxBytes;
10
+ #open;
11
+ #startTail = "";
12
+ /** With `maxBytes`, a paste beyond it is reported as oversized and its text is dropped. */
13
+ constructor(maxBytes) {
14
+ this.#maxBytes = maxBytes;
15
+ }
16
+ /** A paste has started and its end marker has not arrived. */
17
+ get open() {
18
+ return this.#open !== undefined;
19
+ }
20
+ clear() {
21
+ this.#open = undefined;
22
+ this.#startTail = "";
23
+ }
24
+ /**
25
+ * Feed input that continues the open paste, or that begins with the start marker. Returns the
26
+ * completed paste, or undefined while the end marker is still to come.
27
+ */
28
+ feed(data) {
29
+ return this.#feed(data, true);
30
+ }
31
+ /**
32
+ * Drop any bracketed paste in `data`, forwarding the input before and after it. False means
33
+ * `data` holds no paste and nothing was forwarded, so the caller handles it as ordinary input.
34
+ */
35
+ discard(data, forward) {
36
+ if (this.#open !== undefined) {
37
+ const done = this.#feed(data, false);
38
+ if (done !== undefined && done.remaining.length > 0)
39
+ forward(done.remaining);
40
+ return true;
41
+ }
42
+ const hadStartTail = this.#startTail.length > 0;
43
+ const candidate = this.#startTail + data;
44
+ this.#startTail = "";
45
+ const start = candidate.indexOf(PASTE_START);
46
+ if (start >= 0) {
47
+ if (start > 0)
48
+ forward(candidate.slice(0, start));
49
+ const done = this.#feed(candidate.slice(start), false);
50
+ if (done !== undefined && done.remaining.length > 0)
51
+ forward(done.remaining);
52
+ return true;
53
+ }
54
+ const partial = delimiterTail(candidate, PASTE_START);
55
+ // Compatibility: standalone Escape still cancels; marker fragments stay pending.
56
+ if (partial.length > 1) {
57
+ const before = candidate.slice(0, candidate.length - partial.length);
58
+ if (before.length > 0)
59
+ forward(before);
60
+ this.#startTail = partial;
61
+ return true;
62
+ }
63
+ if (hadStartTail) {
64
+ forward(candidate);
65
+ return true;
66
+ }
67
+ return false;
68
+ }
69
+ #feed(data, collect) {
70
+ if (this.#open === undefined) {
71
+ this.#open = { parts: [], piece: "", tail: "", bytes: 0, oversized: false };
72
+ data = data.slice(data.indexOf(PASTE_START) + PASTE_START.length);
73
+ }
74
+ // Protocol: inspect only the new fragment plus a delimiter-sized suffix, not the whole growing body.
75
+ const pending = this.#open;
76
+ const fragment = pending.tail + data;
77
+ const end = fragment.indexOf(PASTE_END);
78
+ const append = (part) => {
79
+ if (!collect || pending.oversized || part.length === 0)
80
+ return;
81
+ if (this.#maxBytes !== undefined) {
82
+ pending.bytes += Buffer.byteLength(part);
83
+ if (pending.bytes > this.#maxBytes) {
84
+ pending.oversized = true;
85
+ pending.parts = [];
86
+ pending.piece = "";
87
+ return;
88
+ }
89
+ }
90
+ // Performance: bound fragment metadata even if a terminal supplies one character per input chunk.
91
+ pending.piece += part;
92
+ if (pending.piece.length >= 16_384) {
93
+ pending.parts.push(pending.piece);
94
+ pending.piece = "";
95
+ }
96
+ };
97
+ if (end < 0) {
98
+ let cut = Math.max(0, fragment.length - (PASTE_END.length - 1));
99
+ if (cut > 0 && /[\uD800-\uDBFF]/u.test(fragment[cut - 1]) && /[\uDC00-\uDFFF]/u.test(fragment[cut]))
100
+ cut--;
101
+ append(fragment.slice(0, cut));
102
+ pending.tail = fragment.slice(cut);
103
+ return undefined;
104
+ }
105
+ append(fragment.slice(0, end));
106
+ this.#open = undefined;
107
+ return {
108
+ text: pending.oversized ? "" : pending.parts.join("") + pending.piece,
109
+ bytes: pending.bytes,
110
+ oversized: pending.oversized,
111
+ remaining: fragment.slice(end + PASTE_END.length),
112
+ };
113
+ }
114
+ }
115
+ function delimiterTail(text, delimiter) {
116
+ const limit = Math.min(text.length, delimiter.length - 1);
117
+ for (let length = limit; length > 0; length -= 1) {
118
+ const suffix = text.slice(text.length - length);
119
+ if (delimiter.startsWith(suffix))
120
+ return suffix;
121
+ }
122
+ return "";
123
+ }