@timurproko/a1 0.2.7-dev.820 → 0.2.7-dev.834
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/README.md +7 -1
- package/bin/automatic-update.js +7 -0
- package/bin/ui.js +1 -0
- package/dist/app/session-shell/session-shell-root.d.ts +8 -1
- package/dist/app/session-shell/session-shell.js +68 -18
- package/dist/app/session-shell/shell-host.js +6 -3
- package/dist/app/session-shell/transcript-document.js +1 -1
- package/dist/composition/owned-ui.d.ts +3 -11
- package/dist/composition/owned-ui.js +93 -42
- package/dist/composition/resident-tab-bridge.d.ts +10 -1
- package/dist/composition/resident-tab-bridge.js +21 -0
- package/dist/composition/settings-route-host.d.ts +12 -2
- package/dist/composition/settings-route-host.js +27 -9
- package/dist/composition/settings-search-input.d.ts +9 -0
- package/dist/composition/settings-search-input.js +58 -0
- package/dist/contracts/owned-ui/session-backend.d.ts +2 -0
- package/dist/contracts/owned-ui/session-workflows.d.ts +7 -3
- package/dist/contracts/presentation/index.d.ts +11 -0
- package/dist/contracts/presentation/index.js +12 -0
- package/dist/features/launch/development-launch.js +6 -0
- package/dist/features/launch/profile-paths.js +4 -0
- package/dist/features/owned-ui/plus-menu-dialog.d.ts +22 -0
- package/dist/features/owned-ui/plus-menu-dialog.js +76 -0
- package/dist/features/owned-ui/reference-screen-app.d.ts +2 -0
- package/dist/features/owned-ui/reference-screen-app.js +12 -2
- package/dist/features/owned-ui/settings-app.d.ts +10 -3
- package/dist/features/owned-ui/settings-app.js +98 -45
- package/dist/features/owned-ui/settings-search-input.d.ts +29 -0
- package/dist/features/owned-ui/settings-search-input.js +1 -0
- package/dist/features/prompt-history/index.d.ts +1 -1
- package/dist/features/prompt-history/paths.d.ts +2 -1
- package/dist/features/prompt-history/paths.js +10 -2
- package/dist/features/prompt-history/service.d.ts +5 -0
- package/dist/features/prompt-history/service.js +1 -1
- package/dist/features/resident-tab/shortcut-declarations.d.ts +1 -1
- package/dist/features/resident-tab/shortcut-declarations.js +2 -0
- package/dist/foundation/launch-context/index.d.ts +2 -0
- package/dist/foundation/launch-context/index.js +1 -0
- package/dist/foundation/release/automatic-update.d.ts +37 -0
- package/dist/foundation/release/automatic-update.js +265 -0
- package/dist/foundation/release/bootstrap.d.ts +1 -9
- package/dist/foundation/release/bootstrap.js +7 -3
- package/dist/foundation/release/index.d.ts +1 -1
- package/dist/foundation/release/index.js +1 -1
- package/dist/foundation/release/latest-release.d.ts +3 -2
- package/dist/foundation/release/latest-release.js +1 -1
- package/dist/foundation/release/update-execution-lease.d.ts +36 -0
- package/dist/foundation/release/update-execution-lease.js +145 -0
- package/dist/foundation/release/update.d.ts +8 -6
- package/dist/foundation/release/update.js +232 -167
- package/dist/foundation/resident-tabs/bridge-client.d.ts +8 -2
- package/dist/foundation/resident-tabs/bridge-client.js +13 -6
- package/dist/foundation/resident-tabs/progress-observation-schema.d.ts +34 -0
- package/dist/foundation/resident-tabs/progress-observation-schema.js +1 -0
- package/dist/foundation/resident-tabs/progress-observation.d.ts +3 -6
- package/dist/foundation/resident-tabs/progress-observation.js +42 -12
- package/dist/foundation/resident-tabs/routes.d.ts +5 -0
- package/dist/foundation/resident-tabs/routes.js +5 -0
- package/dist/integrations/pi/components/editor-interaction.d.ts +2 -0
- package/dist/integrations/pi/components/modal-frame.d.ts +17 -1
- package/dist/integrations/pi/components/modal-frame.js +48 -9
- package/dist/integrations/pi/components/models-dialog.js +4 -52
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +3 -0
- package/dist/integrations/pi/components/owned-editor-ux.js +75 -4
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +57 -37
- package/dist/integrations/pi/components/shell-footer-status.js +19 -3
- package/dist/integrations/pi/components/shell-presenters-transcript.js +4 -1
- package/dist/integrations/pi/components/shell-selectors-dialogs.js +4 -1
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -2
- package/dist/integrations/pi/components/skills-dialog.js +2 -2
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +7 -5
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +15 -8
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +3 -3
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +2 -2
- package/dist/integrations/pi/components/upstream/components/session-footer.js +26 -23
- package/dist/integrations/pi/components/upstream/components/session-selector.js +7 -6
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +7 -6
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +9 -8
- package/dist/integrations/pi/components/upstream/components/tool-execution.js +6 -4
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +8 -8
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +10 -10
- package/dist/integrations/pi/components/upstream/history/editor-core.js +2 -0
- package/dist/integrations/pi/engine/adapter.js +7 -2
- package/dist/integrations/pi/engine/ports.d.ts +1 -1
- package/dist/integrations/pi/engine/ports.js +4 -0
- package/dist/integrations/pi/engine/workflow-runner.js +9 -2
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/darwin-arm64/terminal-host.manifest.json +3 -3
- package/dist/native/darwin-arm64/terminal-host.node +0 -0
- package/dist/native/darwin-x64/manifest.json +1 -1
- package/dist/native/darwin-x64/terminal-host.manifest.json +3 -3
- package/dist/native/darwin-x64/terminal-host.node +0 -0
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/linux-x64/terminal-host.manifest.json +3 -3
- package/dist/native/linux-x64/terminal-host.node +0 -0
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.node +0 -0
- package/dist/native/win32-x64/terminal-host.manifest.json +3 -3
- package/dist/native/win32-x64/terminal-host.node +0 -0
- package/dist/product-identity.d.ts +1 -1
- package/dist/product-identity.js +1 -1
- package/dist/product-identity.json +1 -0
- package/dist/ui/apps/host.d.ts +2 -0
- package/dist/ui/apps/host.js +7 -1
- package/dist/ui/components/dialog-panel.js +35 -30
- package/dist/ui/components/line-input.d.ts +3 -0
- package/dist/ui/components/line-input.js +3 -1
- package/dist/ui/components/prompt-input.d.ts +3 -2
- package/dist/ui/components/prompt-input.js +6 -4
- package/dist/ui/components/spans.js +2 -2
- package/dist/ui/components/surface.js +4 -2
- package/dist/ui/components/text.d.ts +3 -6
- package/dist/ui/components/text.js +6 -8
- package/dist/ui/settings/declarations.d.ts +54 -8
- package/dist/ui/settings/declarations.js +36 -7
- package/dist/ui/settings/manager.js +7 -3
- package/dist/ui/settings/migrations.js +15 -0
- package/dist/ui/settings/sections.d.ts +4 -0
- package/dist/ui/settings/sections.js +9 -3
- package/docs/architecture/resident-tabs.md +36 -8
- package/docs/architecture/tool-image-presentation.md +2 -1
- package/docs/features/prompt-history.md +41 -20
- package/docs/validation.md +25 -5
- package/package.json +1 -1
|
@@ -17,6 +17,7 @@ import { materializeRelease, readMaterializedRelease } from "./release-store.js"
|
|
|
17
17
|
import { scheduleReleaseCleanup } from "./release-gc.js";
|
|
18
18
|
import { UPDATE_ACTIVATION_CONTRACT, activateInstalledRelease, delegateActivation, readActivationContracts, } from "./update-activation.js";
|
|
19
19
|
import { UpdateTransactionStore } from "./update-transaction.js";
|
|
20
|
+
import { acquireUpdateExecutionLease } from "./update-execution-lease.js";
|
|
20
21
|
import { npmPrefixForGlobalRoot, removeUpdateRecoveryCapsule, resolveNpmCli, runProtectedPackageReplacement, updateLauncherPaths, updateNpmInstallArguments, } from "./update-recovery.js";
|
|
21
22
|
export const PRODUCT_PACKAGE = PRODUCT_TEXT.packageName;
|
|
22
23
|
const UPDATE_DIST_TAGS = { stable: "latest", next: "next" };
|
|
@@ -34,6 +35,7 @@ const defaultFileSystem = {
|
|
|
34
35
|
* The update therefore keeps checking for this long before it calls the package locked.
|
|
35
36
|
*/
|
|
36
37
|
export const PACKAGE_UNLOCK_PATIENCE_MS = 15_000;
|
|
38
|
+
export const UPDATE_ALREADY_RUNNING_EXIT_CODE = 75;
|
|
37
39
|
const PACKAGE_UNLOCK_INITIAL_DELAY_MS = 100;
|
|
38
40
|
const PACKAGE_UNLOCK_MAX_DELAY_MS = 1_000;
|
|
39
41
|
// Platform: Windows reports a held directory as EPERM or EBUSY; EACCES and ENOTEMPTY are the other
|
|
@@ -85,6 +87,21 @@ export function createNpmProcessRunner(platform = process.platform) {
|
|
|
85
87
|
* its own endpoint. A cohort running from the mutable installation is the one exception,
|
|
86
88
|
* because that installation is exactly what is about to be replaced under it.
|
|
87
89
|
*/
|
|
90
|
+
async function verifyAutomaticUpdateSafety(paths, stateStore, environment) {
|
|
91
|
+
const state = await stateStore.read();
|
|
92
|
+
const owner = await readActiveEndpoint(paths, state.references.active, environment);
|
|
93
|
+
if (!owner)
|
|
94
|
+
return;
|
|
95
|
+
const ownership = await probeOwnership(owner.metadata);
|
|
96
|
+
if (ownership === "dead")
|
|
97
|
+
return;
|
|
98
|
+
if (ownership !== "live-verified") {
|
|
99
|
+
throw new UpdateAutomaticDeferred(PRODUCT_TEXT.diagnostic(`automatic update deferred because supervisor ownership is ${ownership}`));
|
|
100
|
+
}
|
|
101
|
+
if (!await canonicalImmutableRoot(paths.dataDir, owner.metadata.releaseRoot)) {
|
|
102
|
+
throw new UpdateAutomaticDeferred(PRODUCT_TEXT.diagnostic("automatic update deferred because the running session is not using a retained immutable release; run the update command manually"));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
88
105
|
export function planUpdateOwnership(ownership, runsFromRetainedRelease) {
|
|
89
106
|
if (ownership === "dead")
|
|
90
107
|
return "clean-dead-record";
|
|
@@ -111,13 +128,13 @@ export function createUpdateLifecycleCoordinator(environment = process.env, file
|
|
|
111
128
|
const stateStore = new CohortStateStore(paths.dataDir);
|
|
112
129
|
return {
|
|
113
130
|
async targetIsActive(targetVersion) {
|
|
114
|
-
const state = await stateStore.read();
|
|
115
|
-
const activeId = state.references.active;
|
|
131
|
+
const state = await stateStore.read(), activeId = state.references.active;
|
|
116
132
|
if (!activeId || state.releases[activeId]?.packageVersion !== targetVersion)
|
|
117
133
|
return false;
|
|
118
134
|
const endpoint = await readActiveEndpoint(paths, activeId, environment);
|
|
119
135
|
return endpoint?.metadata.releaseId === activeId && await probeOwnership(endpoint.metadata) === "live-verified";
|
|
120
136
|
},
|
|
137
|
+
async verifyAutomaticSafety() { await verifyAutomaticUpdateSafety(paths, stateStore, environment); },
|
|
121
138
|
async shutdownVerifiedOwners(targetVersion) {
|
|
122
139
|
const state = await stateStore.read();
|
|
123
140
|
const activeId = state.references.active;
|
|
@@ -455,9 +472,58 @@ async function launchersOwnPackage(globalRoot, fileSystem) {
|
|
|
455
472
|
return false;
|
|
456
473
|
}
|
|
457
474
|
}
|
|
475
|
+
async function resolveSelfUpdateTarget(input) {
|
|
476
|
+
if (input.exactTarget !== undefined && input.exactTarget.length > 0) {
|
|
477
|
+
return resolveExactAutomaticTarget(input.exactTarget, input.channel, input.output);
|
|
478
|
+
}
|
|
479
|
+
return input.requested === undefined || input.requested.length === 0
|
|
480
|
+
? await resolveChannelHead(input.runner, input.distTag, input.output)
|
|
481
|
+
: await resolveRequestedPreview(input.runner, input.requested, input.output);
|
|
482
|
+
}
|
|
483
|
+
function resolveExactAutomaticTarget(target, channel, output) {
|
|
484
|
+
const version = validSemver(target);
|
|
485
|
+
const expected = channel === "stable" ? "stable" : "development";
|
|
486
|
+
if (version === null || releaseChannelOf(version) !== expected) {
|
|
487
|
+
output.stderr(`${PRODUCT_TEXT.diagnostic(`refused invalid automatic ${expected} update target ${JSON.stringify(target)}`)}\n`);
|
|
488
|
+
return { version: null, exitCode: 1 };
|
|
489
|
+
}
|
|
490
|
+
return { version, exitCode: 0 };
|
|
491
|
+
}
|
|
458
492
|
function reportUnmanagedInstallation(output, packageRoot, activeGlobalRoot) {
|
|
459
493
|
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`);
|
|
460
494
|
}
|
|
495
|
+
async function withUpdateExecutionLease(input, options, output, operation) {
|
|
496
|
+
const lease = await acquireUpdateExecutionLease(input);
|
|
497
|
+
if (lease.status === "busy") {
|
|
498
|
+
options.onLeaseBusy?.(lease.owner);
|
|
499
|
+
if ((options.mode ?? "manual") === "manual") {
|
|
500
|
+
output.stderr(`${PRODUCT_TEXT.diagnostic(`update already in progress for ${lease.owner.targetVersion}`)}\n`);
|
|
501
|
+
}
|
|
502
|
+
return UPDATE_ALREADY_RUNNING_EXIT_CODE;
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
options.onLeaseAcquired?.(lease.owner);
|
|
506
|
+
return await operation();
|
|
507
|
+
}
|
|
508
|
+
finally {
|
|
509
|
+
await lease.release();
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
async function readRunningPackageVersion(packageRoot, fileSystem, output) {
|
|
513
|
+
try {
|
|
514
|
+
const packageJson = JSON.parse(await fileSystem.readFile(resolve(packageRoot, "package.json")));
|
|
515
|
+
const version = typeof packageJson.version === "string" ? validSemver(packageJson.version) : null;
|
|
516
|
+
if (packageJson.name !== PRODUCT_PACKAGE)
|
|
517
|
+
throw new Error(`package.json does not identify ${PRODUCT_PACKAGE}`);
|
|
518
|
+
if (version === null)
|
|
519
|
+
throw new Error("package.json does not contain a valid semantic version");
|
|
520
|
+
return version;
|
|
521
|
+
}
|
|
522
|
+
catch (error) {
|
|
523
|
+
output.stderr(`${PRODUCT_TEXT.diagnostic(`could not read its running package version: ${errorMessage(error)}`)}\n`);
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
461
527
|
export async function runSelfUpdate(options) {
|
|
462
528
|
const fileSystem = options.fileSystem ?? defaultFileSystem;
|
|
463
529
|
const output = options.output ?? defaultOutput;
|
|
@@ -474,24 +540,14 @@ export async function runSelfUpdate(options) {
|
|
|
474
540
|
options.onPhaseTiming?.({ phase, durationMs: Math.max(0, now() - startedAt) });
|
|
475
541
|
}
|
|
476
542
|
};
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const packageJson = JSON.parse(await measure("package-version", async () => await fileSystem.readFile(resolve(options.packageRoot, "package.json"))));
|
|
480
|
-
const parsedVersion = typeof packageJson.version === "string" ? validSemver(packageJson.version) : null;
|
|
481
|
-
if (packageJson.name !== PRODUCT_PACKAGE)
|
|
482
|
-
throw new Error(`package.json does not identify ${PRODUCT_PACKAGE}`);
|
|
483
|
-
if (parsedVersion === null)
|
|
484
|
-
throw new Error("package.json does not contain a valid semantic version");
|
|
485
|
-
runningVersion = parsedVersion;
|
|
486
|
-
}
|
|
487
|
-
catch (error) {
|
|
488
|
-
output.stderr(`${PRODUCT_TEXT.diagnostic(`could not read its running package version: ${errorMessage(error)}`)}\n`);
|
|
543
|
+
const runningVersion = await measure("package-version", async () => await readRunningPackageVersion(options.packageRoot, fileSystem, output));
|
|
544
|
+
if (runningVersion === null)
|
|
489
545
|
return 1;
|
|
490
|
-
}
|
|
491
546
|
const requested = options.target?.trim();
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
547
|
+
const exactTarget = options.resolvedTarget?.trim();
|
|
548
|
+
const resolved = await measure("target-resolution", async () => await resolveSelfUpdateTarget({
|
|
549
|
+
runner, output, channel, distTag, requested, exactTarget,
|
|
550
|
+
}));
|
|
495
551
|
if (resolved.version === null)
|
|
496
552
|
return resolved.exitCode;
|
|
497
553
|
const targetVersion = resolved.version;
|
|
@@ -515,169 +571,176 @@ export async function runSelfUpdate(options) {
|
|
|
515
571
|
const paths = resolveProductPaths(environment);
|
|
516
572
|
const lifecycle = options.lifecycle ?? createUpdateLifecycleCoordinator(environment, fileSystem, output);
|
|
517
573
|
const transactionStore = options.transactionStore ?? new UpdateTransactionStore(paths.dataDir);
|
|
574
|
+
const mode = options.mode ?? "manual";
|
|
518
575
|
const maintenance = options.maintenance ?? (options.lifecycle
|
|
519
576
|
? async () => { }
|
|
520
577
|
: async () => await scheduleReleaseCleanup(paths.dataDir, paths));
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
if (preflightRequired) {
|
|
533
|
-
try {
|
|
534
|
-
npmCli = await (options.npmCliResolver ?? resolveNpmCli)(npmCliRoot, environment);
|
|
578
|
+
return await withUpdateExecutionLease({ dataDir: paths.dataDir, packageRoot, channel, targetVersion, mode }, options, output, async () => {
|
|
579
|
+
let transaction = await transactionStore.read();
|
|
580
|
+
let npmCli;
|
|
581
|
+
try {
|
|
582
|
+
if (mode === "automatic")
|
|
583
|
+
await lifecycle.verifyAutomaticSafety?.();
|
|
584
|
+
if ((!transaction || transaction.status === "completed") && await lifecycle.targetIsActive(targetVersion)) {
|
|
585
|
+
await maintenance();
|
|
586
|
+
await transactionStore.clearCompleted();
|
|
587
|
+
output.stdout(`${PRODUCT_TEXT.commandName} is up to date — no update needed.\n`);
|
|
588
|
+
return 0;
|
|
535
589
|
}
|
|
536
|
-
|
|
537
|
-
|
|
590
|
+
const preflightRequired = options.npmCliResolver !== undefined
|
|
591
|
+
|| options.packageReplacement === undefined && options.runner === undefined;
|
|
592
|
+
if (preflightRequired) {
|
|
593
|
+
try {
|
|
594
|
+
npmCli = await (options.npmCliResolver ?? resolveNpmCli)(npmCliRoot, environment);
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
throw new UpdatePreflightFailure(errorMessage(error));
|
|
598
|
+
}
|
|
538
599
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
priorActiveReleaseId,
|
|
560
|
-
});
|
|
561
|
-
if (phaseBefore(transaction.phase, "ownership-released")) {
|
|
562
|
-
await measure("ownership-release", async () => {
|
|
563
|
-
await lifecycle.shutdownVerifiedOwners(targetVersion);
|
|
564
|
-
await lifecycle.verifyPackageUnlocked(packageRoot);
|
|
600
|
+
// Rationale: the bar first appears only after every non-mutating replacement prerequisite passes.
|
|
601
|
+
progress.set(3, 15);
|
|
602
|
+
const cohortStore = new CohortStateStore(paths.dataDir);
|
|
603
|
+
let cohortState = await cohortStore.read();
|
|
604
|
+
let priorActiveReleaseId = cohortState.references.active;
|
|
605
|
+
if (options.runner === undefined && (priorActiveReleaseId === null || cohortState.releases[priorActiveReleaseId]?.approval !== "approved")) {
|
|
606
|
+
// Invariant: protected replacement needs an immutable launch authority that npm cannot rename.
|
|
607
|
+
const recoveryRelease = await materializeRelease(packageRoot, paths.dataDir);
|
|
608
|
+
await cohortStore.recordCandidate(recoveryRelease);
|
|
609
|
+
const diagnostics = await certifyMaterializedRelease(recoveryRelease, paths.dataDir);
|
|
610
|
+
await cohortStore.approve(recoveryRelease.releaseId, diagnostics);
|
|
611
|
+
await cohortStore.activate(recoveryRelease.releaseId);
|
|
612
|
+
cohortState = await cohortStore.read();
|
|
613
|
+
priorActiveReleaseId = recoveryRelease.releaseId;
|
|
614
|
+
}
|
|
615
|
+
transaction = await transactionStore.begin({
|
|
616
|
+
channel,
|
|
617
|
+
targetVersion,
|
|
618
|
+
packageRoot,
|
|
619
|
+
priorActiveReleaseId,
|
|
565
620
|
});
|
|
566
|
-
transaction
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
621
|
+
if (phaseBefore(transaction.phase, "ownership-released")) {
|
|
622
|
+
await measure("ownership-release", async () => {
|
|
623
|
+
await lifecycle.shutdownVerifiedOwners(targetVersion);
|
|
624
|
+
await lifecycle.verifyPackageUnlocked(packageRoot);
|
|
625
|
+
});
|
|
626
|
+
transaction = await transactionStore.advance("ownership-released");
|
|
627
|
+
}
|
|
628
|
+
progress.set(15, 70);
|
|
629
|
+
if (phaseBefore(transaction.phase, "package-installed")) {
|
|
630
|
+
if (options.packageReplacement || options.runner === undefined) {
|
|
631
|
+
const priorReleaseId = transaction.priorActiveReleaseId ?? priorActiveReleaseId;
|
|
632
|
+
const recordedPrior = priorReleaseId === null ? undefined : cohortState.releases[priorReleaseId];
|
|
633
|
+
if ((!recordedPrior || recordedPrior.approval !== "approved") && !options.packageReplacement) {
|
|
634
|
+
throw new Error(PRODUCT_TEXT.diagnostic("cannot protect package replacement because no approved prior release is available"));
|
|
635
|
+
}
|
|
636
|
+
const priorRelease = recordedPrior ?? {
|
|
637
|
+
releaseId: priorReleaseId ?? "injected-prior-release",
|
|
638
|
+
releaseRoot: packageRoot,
|
|
639
|
+
contentDigest: "0".repeat(64),
|
|
640
|
+
};
|
|
641
|
+
const replacementTransaction = transaction;
|
|
642
|
+
const replacement = await measure("npm-install", async () => await (options.packageReplacement ?? runProtectedPackageReplacement)({
|
|
643
|
+
dataDir: paths.dataDir,
|
|
644
|
+
globalRoot,
|
|
645
|
+
npmCliRoot,
|
|
646
|
+
...(npmCli === undefined ? {} : { npmCli }),
|
|
647
|
+
packageRoot,
|
|
648
|
+
transaction: replacementTransaction,
|
|
649
|
+
priorRelease,
|
|
650
|
+
output,
|
|
651
|
+
environment,
|
|
652
|
+
onRecoveryState: async (recovery) => {
|
|
653
|
+
if (transactionStore.setRecovery)
|
|
654
|
+
transaction = await transactionStore.setRecovery(recovery) ?? transaction;
|
|
655
|
+
},
|
|
656
|
+
}));
|
|
657
|
+
// Rationale: npm's own text explains a failed replacement and says nothing about a
|
|
658
|
+
// successful one, so it reaches the terminal only when the installation did not happen.
|
|
659
|
+
if (replacement.outcome !== "installed")
|
|
660
|
+
reportChildDiagnostics(output, replacement);
|
|
661
|
+
if (replacement.outcome === "installed")
|
|
662
|
+
transaction = await transactionStore.advance("package-installed");
|
|
663
|
+
if (replacement.cancelled) {
|
|
664
|
+
progress.clear();
|
|
665
|
+
output.stderr(`${PRODUCT_TEXT.diagnostic("update cancelled safely; the launcher is available and the transaction can be resumed.")}\n`);
|
|
666
|
+
return 130;
|
|
667
|
+
}
|
|
668
|
+
if (replacement.outcome !== "installed") {
|
|
669
|
+
throw new UpdateFailure(unsuccessfulCode(replacement.npmExitCode), `npm exited with status ${formatExitCode(replacement.npmExitCode)}`);
|
|
670
|
+
}
|
|
575
671
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
globalRoot,
|
|
585
|
-
npmCliRoot,
|
|
586
|
-
...(npmCli === undefined ? {} : { npmCli }),
|
|
587
|
-
packageRoot,
|
|
588
|
-
transaction: replacementTransaction,
|
|
589
|
-
priorRelease,
|
|
590
|
-
output,
|
|
591
|
-
environment,
|
|
592
|
-
onRecoveryState: async (recovery) => {
|
|
593
|
-
if (transactionStore.setRecovery)
|
|
594
|
-
transaction = await transactionStore.setRecovery(recovery) ?? transaction;
|
|
595
|
-
},
|
|
596
|
-
}));
|
|
597
|
-
// Rationale: npm's own text explains a failed replacement and says nothing about a
|
|
598
|
-
// successful one, so it reaches the terminal only when the installation did not happen.
|
|
599
|
-
if (replacement.outcome !== "installed")
|
|
600
|
-
reportChildDiagnostics(output, replacement);
|
|
601
|
-
if (replacement.outcome === "installed")
|
|
672
|
+
else {
|
|
673
|
+
const installation = await measure("npm-install", async () => await runNpm(runner, updateNpmInstallArguments(globalRoot, targetVersion), true, output, "start the global npm installation", false));
|
|
674
|
+
if (installation.result === null)
|
|
675
|
+
throw new UpdateFailure(installation.exitCode, "npm process failed");
|
|
676
|
+
if (installation.result.code !== 0) {
|
|
677
|
+
reportChildDiagnostics(output, installation.result);
|
|
678
|
+
throw new UpdateFailure(unsuccessfulCode(installation.result.code), `npm exited with status ${formatExitCode(installation.result.code)}`);
|
|
679
|
+
}
|
|
602
680
|
transaction = await transactionStore.advance("package-installed");
|
|
603
|
-
if (replacement.cancelled) {
|
|
604
|
-
progress.clear();
|
|
605
|
-
output.stderr(`${PRODUCT_TEXT.diagnostic("update cancelled safely; the launcher is available and the transaction can be resumed.")}\n`);
|
|
606
|
-
return 130;
|
|
607
|
-
}
|
|
608
|
-
if (replacement.outcome !== "installed") {
|
|
609
|
-
throw new UpdateFailure(unsuccessfulCode(replacement.npmExitCode), `npm exited with status ${formatExitCode(replacement.npmExitCode)}`);
|
|
610
681
|
}
|
|
611
682
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
683
|
+
progress.set(70, 75);
|
|
684
|
+
// Concurrency: ownership can be reacquired after an interrupted installation (for
|
|
685
|
+
// example, if bare A1 is launched before the update is resumed). Recheck
|
|
686
|
+
// immediately before activation so recovery cannot start a second cohort.
|
|
687
|
+
await measure("ownership-release", async () => { await lifecycle.shutdownVerifiedOwners(targetVersion); });
|
|
688
|
+
progress.set(75, MATERIALIZE_PROGRESS.from);
|
|
689
|
+
let activationPhaseStartedAt = now();
|
|
690
|
+
await lifecycle.activateInstalled(packageRoot, targetVersion, async (phase) => {
|
|
691
|
+
options.onPhaseTiming?.({ phase, durationMs: Math.max(0, now() - activationPhaseStartedAt) });
|
|
692
|
+
transaction = await transactionStore.advance(phase);
|
|
693
|
+
progress.set(ACTIVATION_PROGRESS[phase].at, ACTIVATION_PROGRESS[phase].creepTo);
|
|
694
|
+
activationPhaseStartedAt = now();
|
|
695
|
+
}, ({ completed, total }) => {
|
|
696
|
+
const span = MATERIALIZE_PROGRESS.to - MATERIALIZE_PROGRESS.from;
|
|
697
|
+
const done = total > 0 ? Math.min(1, completed / total) : 0;
|
|
698
|
+
progress.set(MATERIALIZE_PROGRESS.from + span * done);
|
|
699
|
+
}, state => {
|
|
700
|
+
if (state === "started") {
|
|
701
|
+
activationPhaseStartedAt = now();
|
|
702
|
+
progress.set(98, 99);
|
|
619
703
|
}
|
|
620
|
-
|
|
621
|
-
|
|
704
|
+
else {
|
|
705
|
+
options.onPhaseTiming?.({ phase: "warmup", durationMs: Math.max(0, now() - activationPhaseStartedAt) });
|
|
706
|
+
activationPhaseStartedAt = now();
|
|
707
|
+
progress.set(99);
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
options.onPhaseTiming?.({ phase: "supervisor-verified", durationMs: Math.max(0, now() - activationPhaseStartedAt) });
|
|
711
|
+
const transactionStartedAt = now();
|
|
712
|
+
await transactionStore.advance("supervisor-verified");
|
|
713
|
+
if (transaction.recovery?.capsulePath)
|
|
714
|
+
await removeUpdateRecoveryCapsule(paths.dataDir, transaction.transactionId);
|
|
715
|
+
// Invariant: successful output follows the durable cleanup disposition. Slow recursive
|
|
716
|
+
// removal belongs to the detached worker started by this maintenance coordinator.
|
|
717
|
+
await maintenance();
|
|
718
|
+
// Concurrency: this is the launch cutover. No failure-prone update work follows success.
|
|
719
|
+
await transactionStore.finish("completed");
|
|
720
|
+
await transactionStore.clearCompleted().catch(() => undefined);
|
|
721
|
+
options.onPhaseTiming?.({ phase: "transaction-complete", durationMs: Math.max(0, now() - transactionStartedAt) });
|
|
722
|
+
progress.finish();
|
|
723
|
+
output.stdout(`${PRODUCT_TEXT.commandName} updated successfully to ${targetVersion}\n`);
|
|
724
|
+
return 0;
|
|
622
725
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
options.onPhaseTiming?.({ phase, durationMs: Math.max(0, now() - activationPhaseStartedAt) });
|
|
632
|
-
transaction = await transactionStore.advance(phase);
|
|
633
|
-
progress.set(ACTIVATION_PROGRESS[phase].at, ACTIVATION_PROGRESS[phase].creepTo);
|
|
634
|
-
activationPhaseStartedAt = now();
|
|
635
|
-
}, ({ completed, total }) => {
|
|
636
|
-
const span = MATERIALIZE_PROGRESS.to - MATERIALIZE_PROGRESS.from;
|
|
637
|
-
const done = total > 0 ? Math.min(1, completed / total) : 0;
|
|
638
|
-
progress.set(MATERIALIZE_PROGRESS.from + span * done);
|
|
639
|
-
}, state => {
|
|
640
|
-
if (state === "started") {
|
|
641
|
-
activationPhaseStartedAt = now();
|
|
642
|
-
progress.set(98, 99);
|
|
643
|
-
}
|
|
644
|
-
else {
|
|
645
|
-
options.onPhaseTiming?.({ phase: "warmup", durationMs: Math.max(0, now() - activationPhaseStartedAt) });
|
|
646
|
-
activationPhaseStartedAt = now();
|
|
647
|
-
progress.set(99);
|
|
726
|
+
catch (error) {
|
|
727
|
+
progress.clear();
|
|
728
|
+
const message = errorMessage(error);
|
|
729
|
+
if (error instanceof UpdateAutomaticDeferred)
|
|
730
|
+
return 1;
|
|
731
|
+
if (error instanceof UpdatePreflightFailure) {
|
|
732
|
+
output.stderr(`${PRODUCT_TEXT.diagnostic(`update could not start safely: ${message}. Nothing was changed. Retry with npm x -y -- ${PRODUCT_PACKAGE}-install`)}\n`);
|
|
733
|
+
return 1;
|
|
648
734
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
// removal belongs to the detached worker started by this maintenance coordinator.
|
|
657
|
-
await maintenance();
|
|
658
|
-
// Concurrency: this is the launch cutover. No failure-prone update work follows success.
|
|
659
|
-
await transactionStore.finish("completed");
|
|
660
|
-
await transactionStore.clearCompleted().catch(() => undefined);
|
|
661
|
-
options.onPhaseTiming?.({ phase: "transaction-complete", durationMs: Math.max(0, now() - transactionStartedAt) });
|
|
662
|
-
progress.finish();
|
|
663
|
-
output.stdout(`${PRODUCT_TEXT.commandName} updated successfully to ${targetVersion}\n`);
|
|
664
|
-
return 0;
|
|
665
|
-
}
|
|
666
|
-
catch (error) {
|
|
667
|
-
progress.clear();
|
|
668
|
-
const message = errorMessage(error);
|
|
669
|
-
if (error instanceof UpdatePreflightFailure) {
|
|
670
|
-
output.stderr(`${PRODUCT_TEXT.diagnostic(`update could not start safely: ${message}. Nothing was changed. Retry with npm x -y -- ${PRODUCT_PACKAGE}-install`)}\n`);
|
|
671
|
-
return 1;
|
|
735
|
+
const rollback = options.lifecycle
|
|
736
|
+
? "previous test lifecycle retained"
|
|
737
|
+
: await rollbackPriorCohort(paths.dataDir, environment, transaction?.priorActiveReleaseId ?? null).catch(rollbackError => `rollback failed: ${errorMessage(rollbackError)}`);
|
|
738
|
+
if (transaction)
|
|
739
|
+
await transactionStore.finish(rollback === "rolled back" ? "rolled-back" : "failed", `${message}; ${rollback}`);
|
|
740
|
+
output.stderr(`${PRODUCT_TEXT.diagnostic(`update failed: ${message}. ${rollback}. Diagnostics: ${transactionStore.path}`)}\n`);
|
|
741
|
+
return error instanceof UpdateFailure ? error.exitCode : 1;
|
|
672
742
|
}
|
|
673
|
-
|
|
674
|
-
? "previous test lifecycle retained"
|
|
675
|
-
: await rollbackPriorCohort(paths.dataDir, environment, transaction?.priorActiveReleaseId ?? null).catch(rollbackError => `rollback failed: ${errorMessage(rollbackError)}`);
|
|
676
|
-
if (transaction)
|
|
677
|
-
await transactionStore.finish(rollback === "rolled back" ? "rolled-back" : "failed", `${message}; ${rollback}`);
|
|
678
|
-
output.stderr(`${PRODUCT_TEXT.diagnostic(`update failed: ${message}. ${rollback}. Diagnostics: ${transactionStore.path}`)}\n`);
|
|
679
|
-
return error instanceof UpdateFailure ? error.exitCode : 1;
|
|
680
|
-
}
|
|
743
|
+
});
|
|
681
744
|
}
|
|
682
745
|
export function assertUpdatePerformanceBudget(evidence, maximumPostNpmDurationMs = 30_000) {
|
|
683
746
|
const failures = [];
|
|
@@ -777,6 +840,8 @@ class UpdateFailure extends Error {
|
|
|
777
840
|
}
|
|
778
841
|
class UpdatePreflightFailure extends Error {
|
|
779
842
|
}
|
|
843
|
+
class UpdateAutomaticDeferred extends Error {
|
|
844
|
+
}
|
|
780
845
|
async function canonicalImmutableRoot(dataDir, releaseRoot) {
|
|
781
846
|
try {
|
|
782
847
|
const [store, selected] = await Promise.all([realpath(resolve(dataDir, "releases")), realpath(releaseRoot)]);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { type ResidentTabRoute } from "./routes.js";
|
|
1
2
|
export type ResidentTabStatus = "starting" | "working" | "needs-input" | "done-unseen" | "done" | "aborted" | "error" | "idle" | "unresponsive" | "crashed" | "restarting" | "failed";
|
|
3
|
+
export { residentUtilityRoute, type ResidentTabRoute } from "./routes.js";
|
|
2
4
|
export interface TabBridgeCredentials {
|
|
3
5
|
readonly tabId: string;
|
|
4
6
|
readonly token: string;
|
|
@@ -17,7 +19,7 @@ export type TabBridgeCommand = {
|
|
|
17
19
|
readonly nameSource: "default" | "user" | "auto";
|
|
18
20
|
} | {
|
|
19
21
|
readonly type: "route";
|
|
20
|
-
readonly route:
|
|
22
|
+
readonly route: ResidentTabRoute;
|
|
21
23
|
} | {
|
|
22
24
|
readonly type: "stop";
|
|
23
25
|
readonly deadlineMs: number;
|
|
@@ -59,5 +61,9 @@ export declare class TabBridgeClient {
|
|
|
59
61
|
requestClose(origin?: TabAttachment | null, timeoutMs?: number): Promise<boolean>;
|
|
60
62
|
consumeControllerBoundaryInput(data: string): string;
|
|
61
63
|
close(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Asks the server to act for the client that controls this tab, as `origin`: leave it, restart or close the tab,
|
|
66
|
+
* or open the tab bar's Plus Menu dialog (`configure-plus`).
|
|
67
|
+
*/
|
|
68
|
+
requestAction(action: "detach" | "restart" | "close" | "configure-plus", origin?: TabAttachment | null, timeoutMs?: number): Promise<boolean>;
|
|
62
69
|
}
|
|
63
|
-
export {};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { connect } from "node:net";
|
|
2
2
|
import { encodeResidentFrame, ResidentFrameDecoder } from "./protocol.js";
|
|
3
|
+
import { RESIDENT_TAB_ROUTES } from "./routes.js";
|
|
4
|
+
export { residentUtilityRoute } from "./routes.js";
|
|
5
|
+
const RESIDENT_TAB_ROUTE_SET = new Set(RESIDENT_TAB_ROUTES);
|
|
3
6
|
/** Metadata-only advisory bridge. Terminal input and transcript bytes have no API here. */
|
|
4
7
|
export class TabBridgeClient {
|
|
5
8
|
#options;
|
|
@@ -118,13 +121,13 @@ export class TabBridgeClient {
|
|
|
118
121
|
this.#send("bridge.shortcuts", { assignments: this.#latestShortcuts.map(([action, key]) => [action, key]) });
|
|
119
122
|
}
|
|
120
123
|
requestDetach(origin = this.captureAttachment(), timeoutMs = 1_000) {
|
|
121
|
-
return this
|
|
124
|
+
return this.requestAction("detach", origin, timeoutMs);
|
|
122
125
|
}
|
|
123
126
|
requestRestart(origin = this.captureAttachment(), timeoutMs = 1_000) {
|
|
124
|
-
return this
|
|
127
|
+
return this.requestAction("restart", origin, timeoutMs);
|
|
125
128
|
}
|
|
126
129
|
requestClose(origin = this.captureAttachment(), timeoutMs = 1_000) {
|
|
127
|
-
return this
|
|
130
|
+
return this.requestAction("close", origin, timeoutMs);
|
|
128
131
|
}
|
|
129
132
|
consumeControllerBoundaryInput(data) {
|
|
130
133
|
const prefix = "\x1bP+a1;controller=";
|
|
@@ -232,8 +235,8 @@ export class TabBridgeClient {
|
|
|
232
235
|
this.#emitCommand({ type: "rename", name: message.body["name"], nameSource: source === "auto" || source === "default" ? source : "user" });
|
|
233
236
|
return;
|
|
234
237
|
}
|
|
235
|
-
if (message.type === "bridge.route" && message.body["route"]
|
|
236
|
-
this.#emitCommand({ type: "route", route: "
|
|
238
|
+
if (message.type === "bridge.route" && RESIDENT_TAB_ROUTE_SET.has(message.body["route"])) {
|
|
239
|
+
this.#emitCommand({ type: "route", route: message.body["route"] });
|
|
237
240
|
return;
|
|
238
241
|
}
|
|
239
242
|
if (message.type === "bridge.stop" && typeof message.body["deadlineMs"] === "number") {
|
|
@@ -255,7 +258,11 @@ export class TabBridgeClient {
|
|
|
255
258
|
}
|
|
256
259
|
}
|
|
257
260
|
}
|
|
258
|
-
|
|
261
|
+
/**
|
|
262
|
+
* Asks the server to act for the client that controls this tab, as `origin`: leave it, restart or close the tab,
|
|
263
|
+
* or open the tab bar's Plus Menu dialog (`configure-plus`).
|
|
264
|
+
*/
|
|
265
|
+
async requestAction(action, origin = this.captureAttachment(), timeoutMs = 1_000) {
|
|
259
266
|
if (!this.available || origin === null || (action === "restart" && !this.#supports("bridge-restart")))
|
|
260
267
|
return false;
|
|
261
268
|
const requestId = ++this.#request;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Payload-free fixture schema; type-only imports keep it outside the evaluated startup graph. */
|
|
2
|
+
export type ResidentInputPhase = "receipt" | "semantic-start" | "semantic-end" | "composition-start" | "composition-end" | "write-start" | "write-end";
|
|
3
|
+
export interface ProgressFields {
|
|
4
|
+
readonly eventSequence?: number;
|
|
5
|
+
readonly revision?: number;
|
|
6
|
+
readonly geometryGeneration?: number;
|
|
7
|
+
readonly bytes?: number;
|
|
8
|
+
readonly count?: number;
|
|
9
|
+
readonly eventKind?: string;
|
|
10
|
+
readonly frameId?: number;
|
|
11
|
+
readonly renderCause?: string;
|
|
12
|
+
readonly fragmentCount?: number;
|
|
13
|
+
readonly transactionKind?: "synchronized-output" | "ordinary";
|
|
14
|
+
}
|
|
15
|
+
export interface ProgressRecord extends ProgressFields {
|
|
16
|
+
readonly schema: "a1-resident-progress-observation-v1";
|
|
17
|
+
readonly runId: string;
|
|
18
|
+
readonly role: "child";
|
|
19
|
+
readonly stage: "engine-delivery" | "child-render-write" | "child-input-phase";
|
|
20
|
+
readonly pid: number;
|
|
21
|
+
readonly processInstance: string;
|
|
22
|
+
readonly timestampUnixUs: number;
|
|
23
|
+
readonly clockErrorUs: number;
|
|
24
|
+
readonly sequence: number;
|
|
25
|
+
readonly tabId: string;
|
|
26
|
+
readonly incarnation: number;
|
|
27
|
+
readonly lostBefore: number;
|
|
28
|
+
}
|
|
29
|
+
export interface ResidentProgressObserverPort {
|
|
30
|
+
readonly tabId: string;
|
|
31
|
+
engineDelivery(event: unknown): void;
|
|
32
|
+
inputPhase?(phase: ResidentInputPhase, revision: number): void;
|
|
33
|
+
childRenderWrite(bytes: number, fragments: number, transaction: "synchronized-output" | "ordinary", frameId?: number, renderCause?: string): void;
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
childRenderWrite(bytes: number, fragments: number, transaction: "synchronized-output" | "ordinary", frameId?: number, renderCause?: string): void;
|
|
5
|
-
}
|
|
6
|
-
/** Returns the opt-in payload-free observer for a resident child, or null on ordinary launches. */
|
|
1
|
+
import type { ResidentProgressObserverPort } from "./progress-observation-schema.js";
|
|
2
|
+
export type { ResidentProgressObserverPort } from "./progress-observation-schema.js";
|
|
3
|
+
/** Opt-in payload-free child observer; ordinary launches return null. */
|
|
7
4
|
export declare function residentProgressObserver(environment?: NodeJS.ProcessEnv): ResidentProgressObserverPort | null;
|