@timurproko/a1 0.2.7-dev.1234 → 0.2.7-dev.1236
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/session-shell/dock-chrome.js +8 -4
- package/dist/app/session-shell/transcript-document.js +1 -0
- package/dist/composition/release-update-observation.js +15 -7
- package/dist/contracts/owned-ui/session-workflows.d.ts +1 -1
- package/dist/foundation/release/automatic-update.d.ts +3 -1
- package/dist/foundation/release/automatic-update.js +29 -4
- package/dist/foundation/release/index.d.ts +2 -2
- package/dist/foundation/release/index.js +2 -2
- package/dist/foundation/release/prepared-update-activation.d.ts +3 -0
- package/dist/foundation/release/prepared-update-activation.js +27 -0
- package/dist/foundation/release/update-activation.d.ts +7 -0
- package/dist/foundation/release/update-activation.js +29 -10
- package/dist/foundation/release/update-transaction.d.ts +1 -0
- package/dist/foundation/release/update-transaction.js +5 -0
- package/dist/foundation/release/update.d.ts +3 -0
- package/dist/foundation/release/update.js +30 -12
- package/dist/integrations/pi/components/shell-footer-status.js +9 -4
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +3 -0
- package/dist/integrations/pi/components/shell-shared-facade.js +8 -0
- package/dist/integrations/pi/engine/adapter.js +9 -1
- package/dist/integrations/pi/engine/diagnostics-state.d.ts +1 -0
- package/dist/integrations/pi/engine/diagnostics-state.js +15 -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/package.json +3 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { COPY_ACKNOWLEDGEMENT_DURATION_MS } from "../../ui/components/selection-copy.js";
|
|
2
2
|
import { renderPiShellCommandMessage, renderPiShellReleaseUpdateBanner, renderPiShellStatusText, } from "../../integrations/pi/components/shell-presenters-info.js";
|
|
3
3
|
import { PINNED_PI_LAYOUT, piTheme } from "../../integrations/pi/components/upstream/theme/theme.js";
|
|
4
|
-
import { piShellTruncateToWidth, renderPiShellInstalledUpdateStatus } from "../../integrations/pi/components/shell-shared-facade.js";
|
|
4
|
+
import { piShellTruncateToWidth, renderPiShellBlockedUpdateStatus, renderPiShellInstalledUpdateStatus } from "../../integrations/pi/components/shell-shared-facade.js";
|
|
5
5
|
import { parseReleaseUpdateDiagnostic } from "./transcript-document.js";
|
|
6
6
|
/**
|
|
7
7
|
* The transient chrome around bare A1's dock: the one command notice above the editor, the release
|
|
@@ -84,9 +84,13 @@ export class DockChrome {
|
|
|
84
84
|
const acknowledgement = this.renderCopyAcknowledgement(width);
|
|
85
85
|
if (acknowledgement !== undefined)
|
|
86
86
|
return acknowledgement;
|
|
87
|
-
if (
|
|
88
|
-
return undefined;
|
|
89
|
-
|
|
87
|
+
if (diagnostics.some(diagnostic => diagnostic.code === "release-update-installed")) {
|
|
88
|
+
return renderPiShellInstalledUpdateStatus(width) || undefined;
|
|
89
|
+
}
|
|
90
|
+
if (diagnostics.some(diagnostic => diagnostic.code === "release-update-blocked")) {
|
|
91
|
+
return renderPiShellBlockedUpdateStatus(width) || undefined;
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
90
94
|
}
|
|
91
95
|
renderReleaseBanner(diagnostics, width) {
|
|
92
96
|
if (this.#releaseDismissed)
|
|
@@ -261,6 +261,7 @@ export class TranscriptDocument {
|
|
|
261
261
|
rows.push(...diagnostics
|
|
262
262
|
.filter(diagnostic => diagnostic.code !== "engine-startup" && diagnostic.code !== "project-trust"
|
|
263
263
|
&& diagnostic.code !== "release-update" && diagnostic.code !== "release-update-installed"
|
|
264
|
+
&& diagnostic.code !== "release-update-blocked"
|
|
264
265
|
&& diagnostic.code !== "package-updates" && diagnostic.code !== "changelog-collapsed"
|
|
265
266
|
&& diagnostic.code !== "changelog-expanded")
|
|
266
267
|
.slice(-3)
|
|
@@ -41,14 +41,22 @@ class ReleaseUpdateObservation {
|
|
|
41
41
|
}
|
|
42
42
|
if (input.mode !== "auto")
|
|
43
43
|
return;
|
|
44
|
+
const announce = (status, version) => {
|
|
45
|
+
const actual = input.clientVersion?.();
|
|
46
|
+
const key = `${status}:${version}`;
|
|
47
|
+
if (key === this.#announced || this.#controller.signal.aborted
|
|
48
|
+
|| status !== "blocked-cleared" && actual && !releases.isNewerRelease(version, actual))
|
|
49
|
+
return;
|
|
50
|
+
this.#announced = key;
|
|
51
|
+
input.backend.extensions.announceReleaseUpdate({ status, version });
|
|
52
|
+
};
|
|
44
53
|
const installed = await import("../foundation/release/automatic-update.js")
|
|
45
|
-
.then(module => module.requestAutomaticUpdate({
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
input.backend.extensions.announceReleaseUpdate({ status: "installed", version: installed });
|
|
54
|
+
.then(module => module.requestAutomaticUpdate({
|
|
55
|
+
release, runningVersion, signal: this.#controller.signal,
|
|
56
|
+
onActivationBlocked: version => announce(version === null ? "blocked-cleared" : "blocked", version ?? release.version),
|
|
57
|
+
}));
|
|
58
|
+
if (installed !== null)
|
|
59
|
+
announce("installed", installed);
|
|
52
60
|
}
|
|
53
61
|
async #run() {
|
|
54
62
|
if (this.#running) {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type AvailableRelease, type ReleaseChannel } from "./latest-release.js";
|
|
2
2
|
export declare const AUTOMATIC_UPDATE_STATE_SCHEMA = 1;
|
|
3
3
|
export declare const AUTOMATIC_UPDATE_RETRY_MS: number;
|
|
4
|
+
export declare const AUTOMATIC_UPDATE_ACTIVATION_RETRY_MS: number;
|
|
4
5
|
export interface AutomaticUpdateState {
|
|
5
6
|
readonly schema: typeof AUTOMATIC_UPDATE_STATE_SCHEMA;
|
|
6
7
|
readonly channel: ReleaseChannel;
|
|
7
8
|
readonly targetVersion: string;
|
|
8
|
-
readonly status: "running" | "installed" | "failed";
|
|
9
|
+
readonly status: "running" | "blocked" | "installed" | "failed";
|
|
9
10
|
readonly attemptedAt: number;
|
|
10
11
|
readonly updatedAt: number;
|
|
11
12
|
readonly installedVersion: string | null;
|
|
@@ -31,6 +32,7 @@ export interface AutomaticUpdateRequestOptions {
|
|
|
31
32
|
readonly packageRoot: string;
|
|
32
33
|
};
|
|
33
34
|
readonly activeVersion?: () => Promise<string | null>;
|
|
35
|
+
readonly onActivationBlocked?: (version: string | null) => void;
|
|
34
36
|
}
|
|
35
37
|
/** Start or observe one silent update and resolve with the active newer version once restart is useful. */
|
|
36
38
|
export declare function requestAutomaticUpdate(options: AutomaticUpdateRequestOptions): Promise<string | null>;
|
|
@@ -11,9 +11,10 @@ import { isNewerRelease, releaseChannelOf } from "./latest-release.js";
|
|
|
11
11
|
import { readCertifiedReleaseManifest, resolveReleaseEntryPoint } from "./release-store.js";
|
|
12
12
|
import { acquireUpdateExecutionLease, readUpdateExecutionOwner } from "./update-execution-lease.js";
|
|
13
13
|
import { UpdateTransactionStore } from "./update-transaction.js";
|
|
14
|
-
import { runSelfUpdate, UPDATE_ALREADY_RUNNING_EXIT_CODE } from "./update.js";
|
|
14
|
+
import { runSelfUpdate, UPDATE_ACTIVATION_BLOCKED_EXIT_CODE, UPDATE_ALREADY_RUNNING_EXIT_CODE } from "./update.js";
|
|
15
15
|
export const AUTOMATIC_UPDATE_STATE_SCHEMA = 1;
|
|
16
16
|
export const AUTOMATIC_UPDATE_RETRY_MS = 24 * 60 * 60_000;
|
|
17
|
+
export const AUTOMATIC_UPDATE_ACTIVATION_RETRY_MS = 15 * 60_000;
|
|
17
18
|
const AUTOMATIC_UPDATE_STATE_FILENAME = "automatic-update.json";
|
|
18
19
|
const MAX_STATE_BYTES = 16_384;
|
|
19
20
|
const OBSERVER_POLL_MS = 250;
|
|
@@ -104,6 +105,14 @@ export async function requestAutomaticUpdate(options) {
|
|
|
104
105
|
await store.write(existing).catch(() => undefined);
|
|
105
106
|
}
|
|
106
107
|
}
|
|
108
|
+
const blockedUpdatedAt = existing?.status === "blocked" && existing.channel === options.release.channel
|
|
109
|
+
&& existing.targetVersion === options.release.version ? existing.updatedAt : null;
|
|
110
|
+
const observedBlocked = blockedUpdatedAt !== null;
|
|
111
|
+
if (observedBlocked) {
|
|
112
|
+
options.onActivationBlocked?.(options.release.version);
|
|
113
|
+
if (now() - blockedUpdatedAt >= 0 && now() - blockedUpdatedAt < AUTOMATIC_UPDATE_ACTIVATION_RETRY_MS)
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
107
116
|
if (existing?.status === "failed" && existing.channel === options.release.channel && now() - existing.attemptedAt >= 0
|
|
108
117
|
&& now() - existing.attemptedAt < AUTOMATIC_UPDATE_RETRY_MS
|
|
109
118
|
&& !isNewerRelease(options.release.version, existing.targetVersion))
|
|
@@ -136,6 +145,8 @@ export async function requestAutomaticUpdate(options) {
|
|
|
136
145
|
// Invariant: no worker began this attempt. Do not invent a successful install or an owner.
|
|
137
146
|
await store.write(state({ packageRoot: worker.packageRoot, channel: options.release.channel,
|
|
138
147
|
targetVersion: options.release.version, dataDir: paths.dataDir }, "failed", now(), null, "worker-start-failed")).catch(() => undefined);
|
|
148
|
+
if (observedBlocked)
|
|
149
|
+
options.onActivationBlocked?.(null);
|
|
139
150
|
return null;
|
|
140
151
|
}
|
|
141
152
|
}
|
|
@@ -149,8 +160,15 @@ export async function requestAutomaticUpdate(options) {
|
|
|
149
160
|
if (installed !== null)
|
|
150
161
|
return installed;
|
|
151
162
|
const state = await store.read().catch(() => null);
|
|
152
|
-
if (state?.targetVersion === options.release.version && state.status === "
|
|
163
|
+
if (state?.targetVersion === options.release.version && state.status === "blocked") {
|
|
164
|
+
options.onActivationBlocked?.(state.targetVersion);
|
|
153
165
|
return null;
|
|
166
|
+
}
|
|
167
|
+
if (state?.targetVersion === options.release.version && state.status === "failed" && state.attemptedAt >= startedAt) {
|
|
168
|
+
if (observedBlocked)
|
|
169
|
+
options.onActivationBlocked?.(null);
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
154
172
|
const owner = await readUpdateExecutionOwner(paths.dataDir).catch(() => null);
|
|
155
173
|
if ((owner === null || !processIsAlive(owner.pid)) && now() - startedAt >= WORKER_START_GRACE_MS)
|
|
156
174
|
return null;
|
|
@@ -200,6 +218,12 @@ export async function runAutomaticUpdateWorker(argv, environment = process.env)
|
|
|
200
218
|
});
|
|
201
219
|
if (code === UPDATE_ALREADY_RUNNING_EXIT_CODE)
|
|
202
220
|
return 0;
|
|
221
|
+
if (code === UPDATE_ACTIVATION_BLOCKED_EXIT_CODE) {
|
|
222
|
+
await runningWrite;
|
|
223
|
+
if (acquired)
|
|
224
|
+
await store.write(state(parsed, "blocked", attemptedAt, null, "resident-continuity-blocked"));
|
|
225
|
+
return code;
|
|
226
|
+
}
|
|
203
227
|
if (code !== 0) {
|
|
204
228
|
await runningWrite;
|
|
205
229
|
if (acquired)
|
|
@@ -313,10 +337,11 @@ function parseWorkerArguments(argv) {
|
|
|
313
337
|
}
|
|
314
338
|
function validateState(value) {
|
|
315
339
|
if (value.schema !== AUTOMATIC_UPDATE_STATE_SCHEMA || (value.channel !== "stable" && value.channel !== "development")
|
|
316
|
-
|| validSemver(value.targetVersion) === null || !["running", "installed", "failed"].includes(value.status)
|
|
340
|
+
|| validSemver(value.targetVersion) === null || !["running", "blocked", "installed", "failed"].includes(value.status)
|
|
317
341
|
|| !Number.isFinite(value.attemptedAt) || !Number.isFinite(value.updatedAt)
|
|
318
342
|
|| (value.installedVersion !== null && validSemver(value.installedVersion) === null)
|
|
319
|
-
|| (value.failure !== null && (typeof value.failure !== "string" || value.failure.length > 512))
|
|
343
|
+
|| (value.failure !== null && (typeof value.failure !== "string" || value.failure.length > 512))
|
|
344
|
+
|| value.status === "blocked" && (value.installedVersion !== null || value.failure !== "resident-continuity-blocked")) {
|
|
320
345
|
throw new Error("invalid automatic update state");
|
|
321
346
|
}
|
|
322
347
|
}
|
|
@@ -21,9 +21,9 @@ export { createRestartSeal, readRestartCertifiedRelease, releaseCertificationDoc
|
|
|
21
21
|
export type { RestartSeal, RestartValidationEvent } from "./restart-certification.js";
|
|
22
22
|
export { STABLE_RELEASE_SCHEMA, createStableReleaseEvidence, verifyStableRegistry } from "./stable-release.js";
|
|
23
23
|
export type { StableRegistryState, StableRegistryVerificationOptions, StableReleaseEvidence, StableReleaseEvidenceInput } from "./stable-release.js";
|
|
24
|
-
export { PACKAGE_UNLOCK_PATIENCE_MS, UPDATE_ALREADY_RUNNING_EXIT_CODE, PRODUCT_PACKAGE, assertUpdatePerformanceBudget, createNpmProcessRunner, createUpdateLifecycleCoordinator, isPackageLockError, lockedPackageDiagnostic, planUpdateOwnership, renderUpdateProgressBar, runSelfUpdate, } from "./update.js";
|
|
24
|
+
export { PACKAGE_UNLOCK_PATIENCE_MS, UPDATE_ACTIVATION_BLOCKED_EXIT_CODE, UPDATE_ALREADY_RUNNING_EXIT_CODE, PRODUCT_PACKAGE, assertUpdatePerformanceBudget, createNpmProcessRunner, createUpdateLifecycleCoordinator, isPackageLockError, lockedPackageDiagnostic, planUpdateOwnership, renderUpdateProgressBar, runSelfUpdate, } from "./update.js";
|
|
25
25
|
export type { ProcessRequest, ProcessResult, SelfUpdateOptions, UpdateActivationPhase, UpdateChannel, UpdateFileMetadata, UpdateFileSystem, UpdateLifecycleCoordinator, UpdateMaterializationProgress, UpdateMeasuredPhase, UpdateOutput, UpdateOwnershipAction, UpdatePackageReplacementInput, UpdatePerformanceEvidence, UpdatePhaseTimingEvent, UpdateProcessRunner, UpdateTransactionJournal, UpdateUnlockPatience, } from "./update.js";
|
|
26
|
-
export { UPDATE_ACTIVATION_CONTRACT, UPDATE_ACTIVATION_ENTRY, UPDATE_ACTIVATION_MANIFEST_FIELD, activateInstalledRelease, delegateActivation, readActivationContracts, runActivationEntry, } from "./update-activation.js";
|
|
26
|
+
export { UPDATE_ACTIVATION_CONTRACT, UPDATE_ACTIVATION_ENTRY, UPDATE_ACTIVATION_MANIFEST_FIELD, UpdateActivationBlockedError, activateInstalledRelease, delegateActivation, readActivationContracts, runActivationEntry, } from "./update-activation.js";
|
|
27
27
|
export type { UpdateActivationCallbacks, UpdateActivationEvent, UpdateActivationRequest } from "./update-activation.js";
|
|
28
28
|
export { selectSupervisorLaunchReleaseId, selectUpdateLaunchRelease } from "./update-launch.js";
|
|
29
29
|
export { UPDATE_RECOVERY_SCHEMA, cleanupUpdateRecoveryCapsules, inspectUpdateLauncherSet, npmPrefixForGlobalRoot, prepareUpdateRecoveryCapsule, readUpdateRecoveryCapsule, removeUpdateRecoveryCapsule, runProtectedPackageReplacement, updateLauncherPaths, updateNpmInstallArguments, } from "./update-recovery.js";
|
|
@@ -10,8 +10,8 @@ export { collectRelease, prepareReleaseCleanup, runBoundedReleaseCleanup, runRel
|
|
|
10
10
|
export { RELEASE_MANIFEST_FILENAME, ReleaseRepairUnavailableError, assertCertifiedFilesPresent, assertImmutableExecutionRoot, consumeMaterializationProof, materializeRelease, readCertifiedReleaseManifest, readMaterializedRelease, repairMaterializedRelease, resolveReleaseEntryPoint, verifyMaterializedRelease, } from "./release-store.js";
|
|
11
11
|
export { createRestartSeal, readRestartCertifiedRelease, releaseCertificationDocument, restartSealDigest } from "./restart-certification.js";
|
|
12
12
|
export { STABLE_RELEASE_SCHEMA, createStableReleaseEvidence, verifyStableRegistry } from "./stable-release.js";
|
|
13
|
-
export { PACKAGE_UNLOCK_PATIENCE_MS, UPDATE_ALREADY_RUNNING_EXIT_CODE, PRODUCT_PACKAGE, assertUpdatePerformanceBudget, createNpmProcessRunner, createUpdateLifecycleCoordinator, isPackageLockError, lockedPackageDiagnostic, planUpdateOwnership, renderUpdateProgressBar, runSelfUpdate, } from "./update.js";
|
|
14
|
-
export { UPDATE_ACTIVATION_CONTRACT, UPDATE_ACTIVATION_ENTRY, UPDATE_ACTIVATION_MANIFEST_FIELD, activateInstalledRelease, delegateActivation, readActivationContracts, runActivationEntry, } from "./update-activation.js";
|
|
13
|
+
export { PACKAGE_UNLOCK_PATIENCE_MS, UPDATE_ACTIVATION_BLOCKED_EXIT_CODE, UPDATE_ALREADY_RUNNING_EXIT_CODE, PRODUCT_PACKAGE, assertUpdatePerformanceBudget, createNpmProcessRunner, createUpdateLifecycleCoordinator, isPackageLockError, lockedPackageDiagnostic, planUpdateOwnership, renderUpdateProgressBar, runSelfUpdate, } from "./update.js";
|
|
14
|
+
export { UPDATE_ACTIVATION_CONTRACT, UPDATE_ACTIVATION_ENTRY, UPDATE_ACTIVATION_MANIFEST_FIELD, UpdateActivationBlockedError, activateInstalledRelease, delegateActivation, readActivationContracts, runActivationEntry, } from "./update-activation.js";
|
|
15
15
|
export { selectSupervisorLaunchReleaseId, selectUpdateLaunchRelease } from "./update-launch.js";
|
|
16
16
|
export { UPDATE_RECOVERY_SCHEMA, cleanupUpdateRecoveryCapsules, inspectUpdateLauncherSet, npmPrefixForGlobalRoot, prepareUpdateRecoveryCapsule, readUpdateRecoveryCapsule, removeUpdateRecoveryCapsule, runProtectedPackageReplacement, updateLauncherPaths, updateNpmInstallArguments, } from "./update-recovery.js";
|
|
17
17
|
export { UPDATE_JOURNAL_SCHEMA, UpdateTransactionStore } from "./update-transaction.js";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type UpdateActivationCallbacks, type UpdateActivationRequest } from "./update-activation.js";
|
|
2
|
+
/** Retry only the selection boundary for an exact candidate already materialized and certified. */
|
|
3
|
+
export declare function activatePreparedRelease(request: Omit<UpdateActivationRequest, "packageRoot">, callbacks: UpdateActivationCallbacks): Promise<void>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { PRODUCT_IDENTITY } from "../../product-identity.js";
|
|
3
|
+
import { assertContinuityOrBlock } from "./update-activation.js";
|
|
4
|
+
import { ensureSupervisor } from "./bootstrap.js";
|
|
5
|
+
import { CohortStateStore } from "./cohort-state.js";
|
|
6
|
+
import { resolveProductPaths } from "../lifecycle/index.js";
|
|
7
|
+
import { readCertifiedReleaseManifest } from "./release-store.js";
|
|
8
|
+
/** Retry only the selection boundary for an exact candidate already materialized and certified. */
|
|
9
|
+
export async function activatePreparedRelease(request, callbacks) {
|
|
10
|
+
const stateStore = new CohortStateStore(request.dataDir);
|
|
11
|
+
const state = await stateStore.read();
|
|
12
|
+
const releaseId = state.references.pending;
|
|
13
|
+
const record = releaseId === null ? undefined : state.releases[releaseId];
|
|
14
|
+
if (record === undefined || record.approval !== "approved" || record.packageVersion !== request.targetVersion
|
|
15
|
+
|| state.references.approved !== releaseId) {
|
|
16
|
+
throw new Error(`no certified prepared release matches target ${request.targetVersion}`);
|
|
17
|
+
}
|
|
18
|
+
const candidate = await readCertifiedReleaseManifest(record, resolve(request.dataDir, "releases"));
|
|
19
|
+
const paths = resolveProductPaths(request.environment);
|
|
20
|
+
const continuity = { releaseRoot: candidate.releaseRoot, dataDir: request.dataDir, environment: request.environment,
|
|
21
|
+
profileRoot: request.environment[PRODUCT_IDENTITY.environment.profileHome] ?? paths.configDir, runtimeDir: paths.runtimeDir };
|
|
22
|
+
await assertContinuityOrBlock(continuity);
|
|
23
|
+
await ensureSupervisor(candidate, request.environment);
|
|
24
|
+
await assertContinuityOrBlock(continuity);
|
|
25
|
+
await stateStore.activate(candidate.releaseId);
|
|
26
|
+
await callbacks.phase("active-reference-committed");
|
|
27
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ContinuityOptions } from "../resident-tabs/index.js";
|
|
1
2
|
/**
|
|
2
3
|
* The one activation contract an installed tree can offer the updater that installed it.
|
|
3
4
|
* The updater knows nothing else about the tree: the manifest names the contracts the tree
|
|
@@ -8,6 +9,10 @@ export declare const UPDATE_ACTIVATION_ENTRY = "bin/activate.js";
|
|
|
8
9
|
/** Manifest field listing the activation contracts an installed tree serves. */
|
|
9
10
|
export declare const UPDATE_ACTIVATION_MANIFEST_FIELD = "updateActivationContracts";
|
|
10
11
|
export type UpdateActivationPhase = "materialized" | "certified" | "active-reference-committed";
|
|
12
|
+
/** A certified candidate is safe to retain, but resident control continuity blocks selection. */
|
|
13
|
+
export declare class UpdateActivationBlockedError extends Error {
|
|
14
|
+
readonly code: "resident-continuity-blocked";
|
|
15
|
+
}
|
|
11
16
|
/**
|
|
12
17
|
* Copying the release is the longest step with nothing to say for itself, so it
|
|
13
18
|
* reports the files it has written against the files it must write. A caller that
|
|
@@ -44,6 +49,7 @@ export type UpdateActivationEvent = {
|
|
|
44
49
|
} | {
|
|
45
50
|
readonly event: "failed";
|
|
46
51
|
readonly message: string;
|
|
52
|
+
readonly code?: UpdateActivationBlockedError["code"];
|
|
47
53
|
};
|
|
48
54
|
/**
|
|
49
55
|
* Activate the installed tree at `packageRoot` with this process's own release code:
|
|
@@ -70,3 +76,4 @@ export declare function delegateActivation(request: UpdateActivationRequest, cal
|
|
|
70
76
|
export declare function runActivationEntry(argv: readonly string[], entryUrl: string, io?: {
|
|
71
77
|
write(line: string): void;
|
|
72
78
|
}, environment?: NodeJS.ProcessEnv): Promise<number>;
|
|
79
|
+
export declare function assertContinuityOrBlock(context: ContinuityOptions): Promise<void>;
|
|
@@ -19,6 +19,10 @@ export const UPDATE_ACTIVATION_ENTRY = "bin/activate.js";
|
|
|
19
19
|
/** Manifest field listing the activation contracts an installed tree serves. */
|
|
20
20
|
export const UPDATE_ACTIVATION_MANIFEST_FIELD = "updateActivationContracts";
|
|
21
21
|
const ACTIVATION_DIAGNOSTIC_LIMIT = 2_000;
|
|
22
|
+
/** A certified candidate is safe to retain, but resident control continuity blocks selection. */
|
|
23
|
+
export class UpdateActivationBlockedError extends Error {
|
|
24
|
+
code = "resident-continuity-blocked";
|
|
25
|
+
}
|
|
22
26
|
/**
|
|
23
27
|
* Activate the installed tree at `packageRoot` with this process's own release code:
|
|
24
28
|
* materialize it into the immutable store, certify, warm, verify supervision, and only
|
|
@@ -51,14 +55,16 @@ export async function activateInstalledRelease(request, callbacks) {
|
|
|
51
55
|
callbacks.onWarmup?.("started");
|
|
52
56
|
await warmMaterializedRelease(candidate, request.environment);
|
|
53
57
|
callbacks.onWarmup?.("completed");
|
|
54
|
-
|
|
58
|
+
await completeActivation(candidate, request, callbacks, stateStore);
|
|
59
|
+
}
|
|
60
|
+
async function completeActivation(candidate, request, callbacks, stateStore) {
|
|
55
61
|
const paths = resolveProductPaths(request.environment);
|
|
56
62
|
const continuity = { releaseRoot: candidate.releaseRoot, dataDir: request.dataDir, environment: request.environment,
|
|
57
63
|
profileRoot: request.environment[PRODUCT_IDENTITY.environment.profileHome] ?? paths.configDir, runtimeDir: paths.runtimeDir };
|
|
58
|
-
await
|
|
64
|
+
await assertContinuityOrBlock(continuity);
|
|
59
65
|
await ensureSupervisor(candidate, request.environment);
|
|
60
66
|
// Invariant: revalidate after supervision, before committing client activation or restart readiness.
|
|
61
|
-
await
|
|
67
|
+
await assertContinuityOrBlock(continuity);
|
|
62
68
|
await stateStore.activate(candidate.releaseId);
|
|
63
69
|
await callbacks.phase("active-reference-committed");
|
|
64
70
|
}
|
|
@@ -102,14 +108,14 @@ export async function delegateActivation(request, callbacks) {
|
|
|
102
108
|
events = decoder.push(chunk);
|
|
103
109
|
}
|
|
104
110
|
catch (error) {
|
|
105
|
-
failure ??= errorMessage(error);
|
|
111
|
+
failure ??= new Error(errorMessage(error));
|
|
106
112
|
return;
|
|
107
113
|
}
|
|
108
114
|
for (const event of events) {
|
|
109
115
|
relay = relay.then(async () => {
|
|
110
116
|
const parsed = parseActivationEvent(event);
|
|
111
117
|
if (!parsed) {
|
|
112
|
-
failure ??= `unrecognized activation event ${JSON.stringify(event).slice(0, 200)}
|
|
118
|
+
failure ??= new Error(`unrecognized activation event ${JSON.stringify(event).slice(0, 200)}`);
|
|
113
119
|
return;
|
|
114
120
|
}
|
|
115
121
|
if (parsed.event === "materializing")
|
|
@@ -121,8 +127,10 @@ export async function delegateActivation(request, callbacks) {
|
|
|
121
127
|
else if (parsed.event === "completed")
|
|
122
128
|
completed = true;
|
|
123
129
|
else
|
|
124
|
-
failure ??= parsed.
|
|
125
|
-
|
|
130
|
+
failure ??= parsed.code === "resident-continuity-blocked"
|
|
131
|
+
? new UpdateActivationBlockedError(parsed.message)
|
|
132
|
+
: new Error(parsed.message);
|
|
133
|
+
}).catch(error => { failure ??= new Error(errorMessage(error)); });
|
|
126
134
|
}
|
|
127
135
|
});
|
|
128
136
|
const exit = await new Promise((resolvePromise, rejectPromise) => {
|
|
@@ -131,7 +139,7 @@ export async function delegateActivation(request, callbacks) {
|
|
|
131
139
|
});
|
|
132
140
|
await relay;
|
|
133
141
|
if (failure)
|
|
134
|
-
throw
|
|
142
|
+
throw failure;
|
|
135
143
|
if (exit.code !== 0 || !completed) {
|
|
136
144
|
const status = exit.code === null ? exit.signal ?? "unknown status" : `status ${exit.code}`;
|
|
137
145
|
throw new Error(`installed release activation exited with ${status}${summarize(diagnostics)}`);
|
|
@@ -160,7 +168,7 @@ export async function runActivationEntry(argv, entryUrl, io = { write: line => p
|
|
|
160
168
|
return 0;
|
|
161
169
|
}
|
|
162
170
|
catch (error) {
|
|
163
|
-
emit({ event: "failed", message: errorMessage(error) });
|
|
171
|
+
emit({ event: "failed", message: errorMessage(error), ...(error instanceof UpdateActivationBlockedError ? { code: error.code } : {}) });
|
|
164
172
|
return 1;
|
|
165
173
|
}
|
|
166
174
|
}
|
|
@@ -198,11 +206,22 @@ function parseActivationEvent(value) {
|
|
|
198
206
|
case "completed":
|
|
199
207
|
return { event: "completed" };
|
|
200
208
|
case "failed":
|
|
201
|
-
return typeof record.message === "string"
|
|
209
|
+
return typeof record.message === "string" && (record.code === undefined || record.code === "resident-continuity-blocked")
|
|
210
|
+
? { event: "failed", message: record.message, ...(record.code === undefined ? {} : { code: record.code }) }
|
|
211
|
+
: null;
|
|
202
212
|
default:
|
|
203
213
|
return null;
|
|
204
214
|
}
|
|
205
215
|
}
|
|
216
|
+
export async function assertContinuityOrBlock(context) {
|
|
217
|
+
const { assertResidentContinuity } = await import("../resident-tabs/index.js");
|
|
218
|
+
try {
|
|
219
|
+
await assertResidentContinuity(context);
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
throw new UpdateActivationBlockedError(errorMessage(error), { cause: error });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
206
225
|
function summarize(text) {
|
|
207
226
|
const lines = text.split(/\r?\n/).map(line => line.trim()).filter(line => line.length > 0);
|
|
208
227
|
return lines.length === 0 ? "" : `: ${lines.slice(0, 4).join(" | ")}`;
|
|
@@ -37,6 +37,7 @@ export declare class UpdateTransactionStore {
|
|
|
37
37
|
}): Promise<UpdateTransaction>;
|
|
38
38
|
advance(phase: UpdateTransactionPhase): Promise<UpdateTransaction>;
|
|
39
39
|
setRecovery(recovery: UpdateRecoveryState): Promise<UpdateTransaction>;
|
|
40
|
+
recordActiveError(error: string): Promise<UpdateTransaction>;
|
|
40
41
|
finish(status: "completed" | "rolled-back" | "failed", error?: string | null): Promise<UpdateTransaction>;
|
|
41
42
|
clearCompleted(): Promise<void>;
|
|
42
43
|
}
|
|
@@ -49,6 +49,10 @@ export class UpdateTransactionStore {
|
|
|
49
49
|
const current = await this.#requiredActive();
|
|
50
50
|
return await this.#write({ ...current, recovery, updatedAt: new Date().toISOString() });
|
|
51
51
|
}
|
|
52
|
+
async recordActiveError(error) {
|
|
53
|
+
const current = await this.#requiredActive();
|
|
54
|
+
return await this.#write({ ...current, error: error.replace(/[\r\n]+/gu, " ").slice(0, 2_000), updatedAt: new Date().toISOString() });
|
|
55
|
+
}
|
|
52
56
|
async finish(status, error = null) {
|
|
53
57
|
const current = await this.read();
|
|
54
58
|
if (!current)
|
|
@@ -89,6 +93,7 @@ function validate(value) {
|
|
|
89
93
|
if (value.schema !== UPDATE_JOURNAL_SCHEMA || typeof value.transactionId !== "string" || !["stable", "next"].includes(value.channel)
|
|
90
94
|
|| typeof value.targetVersion !== "string" || typeof value.packageRoot !== "string" || typeof value.startedAt !== "string"
|
|
91
95
|
|| typeof value.updatedAt !== "string" || phaseOrder(value.phase) < 0 || !["active", "completed", "rolled-back", "failed"].includes(value.status)
|
|
96
|
+
|| (value.error !== null && (typeof value.error !== "string" || value.error.length > 2_000))
|
|
92
97
|
|| (value.recovery !== undefined && !validRecovery(value.recovery))) {
|
|
93
98
|
throw new Error(`invalid ${PRODUCT_TEXT.displayName} update transaction journal`);
|
|
94
99
|
}
|
|
@@ -95,6 +95,7 @@ export interface UpdateLifecycleCoordinator {
|
|
|
95
95
|
}>;
|
|
96
96
|
verifyPackageUnlocked(packageRoot: string): Promise<void>;
|
|
97
97
|
activateInstalled(packageRoot: string, targetVersion: string, phase: (phase: UpdateActivationPhase) => Promise<void>, onMaterializing?: (progress: UpdateMaterializationProgress) => void, onWarmup?: (state: "started" | "completed") => void): Promise<void>;
|
|
98
|
+
activatePrepared?(targetVersion: string, phase: (phase: UpdateActivationPhase) => Promise<void>): Promise<void>;
|
|
98
99
|
}
|
|
99
100
|
export interface UpdateTransactionJournal {
|
|
100
101
|
readonly path: string;
|
|
@@ -107,6 +108,7 @@ export interface UpdateTransactionJournal {
|
|
|
107
108
|
}): Promise<UpdateTransaction>;
|
|
108
109
|
advance(phase: UpdateTransactionPhase): Promise<UpdateTransaction>;
|
|
109
110
|
setRecovery?(recovery: UpdateRecoveryState): Promise<UpdateTransaction>;
|
|
111
|
+
recordActiveError?(error: string): Promise<UpdateTransaction>;
|
|
110
112
|
finish(status: "completed" | "rolled-back" | "failed", error?: string | null): Promise<UpdateTransaction>;
|
|
111
113
|
clearCompleted(): Promise<void>;
|
|
112
114
|
}
|
|
@@ -134,6 +136,7 @@ export interface UpdatePackageReplacementInput {
|
|
|
134
136
|
*/
|
|
135
137
|
export declare const PACKAGE_UNLOCK_PATIENCE_MS = 15000;
|
|
136
138
|
export declare const UPDATE_ALREADY_RUNNING_EXIT_CODE = 75;
|
|
139
|
+
export declare const UPDATE_ACTIVATION_BLOCKED_EXIT_CODE = 76;
|
|
137
140
|
/** Whether a rename failure means something holds the tree, as opposed to the tree being wrong. */
|
|
138
141
|
export declare function isPackageLockError(error: unknown): boolean;
|
|
139
142
|
/** What the user reads when the package stayed held for the whole patience window. */
|
|
@@ -16,7 +16,7 @@ import { CohortStateStore } from "./cohort-state.js";
|
|
|
16
16
|
import { cleanupVerifiedOwner, processIsAlive } from "./process-cleanup.js";
|
|
17
17
|
import { materializeRelease, readMaterializedRelease } from "./release-store.js";
|
|
18
18
|
import { scheduleReleaseCleanup } from "./release-gc.js";
|
|
19
|
-
import { UPDATE_ACTIVATION_CONTRACT, activateInstalledRelease, delegateActivation, readActivationContracts, } from "./update-activation.js";
|
|
19
|
+
import { UPDATE_ACTIVATION_CONTRACT, UpdateActivationBlockedError, activateInstalledRelease, delegateActivation, readActivationContracts, } from "./update-activation.js";
|
|
20
20
|
import { UpdateTransactionStore } from "./update-transaction.js";
|
|
21
21
|
import { acquireUpdateExecutionLease, reclaimDeadLease } from "./update-execution-lease.js";
|
|
22
22
|
import { npmPrefixForGlobalRoot, removeUpdateRecoveryCapsule, resolveNpmCli, runProtectedPackageReplacement, updateLauncherPaths, updateNpmInstallArguments, } from "./update-recovery.js";
|
|
@@ -37,6 +37,7 @@ const defaultFileSystem = {
|
|
|
37
37
|
*/
|
|
38
38
|
export const PACKAGE_UNLOCK_PATIENCE_MS = 15_000;
|
|
39
39
|
export const UPDATE_ALREADY_RUNNING_EXIT_CODE = 75;
|
|
40
|
+
export const UPDATE_ACTIVATION_BLOCKED_EXIT_CODE = 76;
|
|
40
41
|
/** A named preview reads the registry's whole package document, which is larger than a dist-tag lookup. */
|
|
41
42
|
const PREVIEW_LOOKUP_TIMEOUT_MS = 30_000;
|
|
42
43
|
const PACKAGE_UNLOCK_INITIAL_DELAY_MS = 100;
|
|
@@ -225,11 +226,7 @@ export function createUpdateLifecycleCoordinator(environment = process.env, file
|
|
|
225
226
|
},
|
|
226
227
|
async activateInstalled(packageRoot, targetVersion, phase, onMaterializing, onWarmup) {
|
|
227
228
|
const request = { packageRoot, dataDir: paths.dataDir, targetVersion, environment };
|
|
228
|
-
const callbacks = {
|
|
229
|
-
phase,
|
|
230
|
-
...(onMaterializing ? { onMaterializing } : {}),
|
|
231
|
-
...(onWarmup ? { onWarmup } : {}),
|
|
232
|
-
};
|
|
229
|
+
const callbacks = { phase, ...(onMaterializing ? { onMaterializing } : {}), ...(onWarmup ? { onWarmup } : {}) };
|
|
233
230
|
// Compatibility: the tree npm just installed is newer than this updater, so its layout is
|
|
234
231
|
// its own to know. A tree that serves the contract activates itself with its own code;
|
|
235
232
|
// a tree from before the contract is activated here, with the layout it had then.
|
|
@@ -239,8 +236,13 @@ export function createUpdateLifecycleCoordinator(environment = process.env, file
|
|
|
239
236
|
else
|
|
240
237
|
await activateInstalledRelease(request, callbacks);
|
|
241
238
|
},
|
|
239
|
+
async activatePrepared(targetVersion, phase) { await retryPreparedActivation(paths.dataDir, environment, targetVersion, phase); },
|
|
242
240
|
};
|
|
243
241
|
}
|
|
242
|
+
async function retryPreparedActivation(dataDir, environment, targetVersion, phase) {
|
|
243
|
+
await (await import("./prepared-update-activation.js"))
|
|
244
|
+
.activatePreparedRelease({ dataDir, targetVersion, environment }, { phase });
|
|
245
|
+
}
|
|
244
246
|
const PROGRESS_BAR_WIDTH = 38;
|
|
245
247
|
const PROGRESS_TICK_MS = 200;
|
|
246
248
|
/**
|
|
@@ -716,16 +718,14 @@ export async function runSelfUpdate(options) {
|
|
|
716
718
|
await measure("ownership-release", async () => { await lifecycle.shutdownVerifiedOwners(targetVersion); });
|
|
717
719
|
progress.set(75, MATERIALIZE_PROGRESS.from);
|
|
718
720
|
let activationPhaseStartedAt = now();
|
|
719
|
-
|
|
721
|
+
const recordActivationPhase = async (phase) => {
|
|
720
722
|
options.onPhaseTiming?.({ phase, durationMs: Math.max(0, now() - activationPhaseStartedAt) });
|
|
721
723
|
transaction = await transactionStore.advance(phase);
|
|
722
724
|
progress.set(ACTIVATION_PROGRESS[phase].at, ACTIVATION_PROGRESS[phase].creepTo);
|
|
723
725
|
activationPhaseStartedAt = now();
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
progress.set(MATERIALIZE_PROGRESS.from + span * done);
|
|
728
|
-
}, state => {
|
|
726
|
+
};
|
|
727
|
+
await activateUpdateTransaction(lifecycle, transaction, packageRoot, targetVersion, recordActivationPhase, ({ completed, total }) => progress.set(MATERIALIZE_PROGRESS.from
|
|
728
|
+
+ (MATERIALIZE_PROGRESS.to - MATERIALIZE_PROGRESS.from) * (total > 0 ? Math.min(1, completed / total) : 0)), state => {
|
|
729
729
|
if (state === "started") {
|
|
730
730
|
activationPhaseStartedAt = now();
|
|
731
731
|
progress.set(98, 99);
|
|
@@ -757,6 +757,9 @@ export async function runSelfUpdate(options) {
|
|
|
757
757
|
const message = errorMessage(error);
|
|
758
758
|
if (error instanceof UpdateAutomaticDeferred)
|
|
759
759
|
return 1;
|
|
760
|
+
const blockedExit = await preparedActivationExit(error, transaction, mode, output, transactionStore);
|
|
761
|
+
if (blockedExit !== null)
|
|
762
|
+
return blockedExit;
|
|
760
763
|
if (error instanceof UpdatePreflightFailure) {
|
|
761
764
|
output.stderr(`${PRODUCT_TEXT.diagnostic(`update could not start safely: ${message}. Nothing was changed. Retry with npm x -y -- ${PRODUCT_PACKAGE}-install`)}\n`);
|
|
762
765
|
return 1;
|
|
@@ -859,6 +862,21 @@ async function rollbackPriorCohort(dataDir, environment, priorReleaseId) {
|
|
|
859
862
|
await ensureSupervisor(release, environment);
|
|
860
863
|
return "rolled back";
|
|
861
864
|
}
|
|
865
|
+
async function activateUpdateTransaction(lifecycle, transaction, packageRoot, targetVersion, phase, materializing, warmup) {
|
|
866
|
+
if (!phaseBefore(transaction.phase, "certified") && phaseBefore(transaction.phase, "active-reference-committed")
|
|
867
|
+
&& lifecycle.activatePrepared !== undefined)
|
|
868
|
+
await lifecycle.activatePrepared(targetVersion, phase);
|
|
869
|
+
else
|
|
870
|
+
await lifecycle.activateInstalled(packageRoot, targetVersion, phase, materializing, warmup);
|
|
871
|
+
}
|
|
872
|
+
async function preparedActivationExit(error, transaction, mode, output, journal) {
|
|
873
|
+
if (!(error instanceof UpdateActivationBlockedError) || transaction === null || phaseBefore(transaction.phase, "certified"))
|
|
874
|
+
return null;
|
|
875
|
+
if (mode === "manual")
|
|
876
|
+
output.stderr(`${PRODUCT_TEXT.diagnostic(`update prepared, but activation is blocked: ${error.message}. The prior client remains selected; run the update again after the resident blocker clears.`)}\n`);
|
|
877
|
+
await journal.recordActiveError?.(error.message);
|
|
878
|
+
return UPDATE_ACTIVATION_BLOCKED_EXIT_CODE;
|
|
879
|
+
}
|
|
862
880
|
function phaseBefore(current, target) {
|
|
863
881
|
const phases = ["shutdown-intent", "ownership-released", "package-installed", "materialized", "certified", "active-reference-committed", "supervisor-verified"];
|
|
864
882
|
return phases.indexOf(current) < phases.indexOf(target);
|
|
@@ -7,7 +7,7 @@ import { KeybindingsManager } from "./upstream/adjacent/core/keybindings.js";
|
|
|
7
7
|
import { StatusIndicator, WorkingStatusIndicator } from "./upstream/components/status-indicator.js";
|
|
8
8
|
import { protectPiPromptChipPresentation } from "./prompt-chip-presentation.js";
|
|
9
9
|
import { PINNED_PI_LAYOUT, piTheme, } from "./theme.js";
|
|
10
|
-
import { createTuiFacade, ensureTheme, renderPiShellInstalledUpdateStatus, piShellVisibleWidth, } from "./shell-shared-facade.js";
|
|
10
|
+
import { createTuiFacade, ensureTheme, renderPiShellBlockedUpdateStatus, renderPiShellInstalledUpdateStatus, piShellVisibleWidth, } from "./shell-shared-facade.js";
|
|
11
11
|
export function createPiShellHeader(options = {}) {
|
|
12
12
|
ensureTheme();
|
|
13
13
|
let expanded = options.expanded ?? false;
|
|
@@ -229,10 +229,15 @@ function statusSignature(view, workingOverride, outputPad, placement, progressPr
|
|
|
229
229
|
return `${placement}\u0000${outputPad}\u0000${view.lifecycle}\u0000${progressPresentation}\u0000${message}\u0000${view.status.diagnostics.at(-1) ?? ""}`;
|
|
230
230
|
}
|
|
231
231
|
function appendInstalledUpdateStatus(rows, width, view, progressPresentation) {
|
|
232
|
-
if (progressPresentation === "custom-viewport" || width <= 0
|
|
233
|
-
|
|
232
|
+
if (progressPresentation === "custom-viewport" || width <= 0)
|
|
233
|
+
return rows;
|
|
234
|
+
const status = view.diagnostics.some(diagnostic => diagnostic.code === "release-update-installed")
|
|
235
|
+
? renderPiShellInstalledUpdateStatus(width)
|
|
236
|
+
: view.diagnostics.some(diagnostic => diagnostic.code === "release-update-blocked")
|
|
237
|
+
? renderPiShellBlockedUpdateStatus(width)
|
|
238
|
+
: "";
|
|
239
|
+
if (status.length === 0)
|
|
234
240
|
return rows;
|
|
235
|
-
const status = renderPiShellInstalledUpdateStatus(width);
|
|
236
241
|
const statusWidth = piShellVisibleWidth(status);
|
|
237
242
|
const target = rows.findLastIndex(row => piShellVisibleWidth(row.trimEnd()) > 0);
|
|
238
243
|
if (target < 0)
|
|
@@ -9,8 +9,11 @@ import { KeybindingsManager } from "./upstream/adjacent/core/keybindings.js";
|
|
|
9
9
|
export declare const piShellVisibleWidth: typeof visibleWidth;
|
|
10
10
|
export declare const piShellTruncateToWidth: (text: string, width: number) => string;
|
|
11
11
|
export declare const INSTALLED_UPDATE_STATUS_TEXT = "Update installed, restart to update";
|
|
12
|
+
export declare const BLOCKED_UPDATE_STATUS_TEXT = "Update prepared; activation blocked";
|
|
12
13
|
/** Shared green restart notice for the pinned status and bare-A1 prompt overlay. */
|
|
13
14
|
export declare function renderPiShellInstalledUpdateStatus(width: number): string;
|
|
15
|
+
/** Sanitized warning for a certified candidate that cannot yet preserve resident continuity. */
|
|
16
|
+
export declare function renderPiShellBlockedUpdateStatus(width: number): string;
|
|
14
17
|
/** A clickable URL in pinned Pi's grammar: an OSC 8 link where the terminal supports one, the plain URL otherwise. */
|
|
15
18
|
export declare const piShellHyperlink: (url: string, text?: string) => string;
|
|
16
19
|
export interface PiShellComponentPort extends PresentationComponentPort {
|
|
@@ -5,6 +5,7 @@ import { ensurePiTheme, piTheme, } from "./theme.js";
|
|
|
5
5
|
export const piShellVisibleWidth = visibleWidth;
|
|
6
6
|
export const piShellTruncateToWidth = (text, width) => truncateToWidth(text, width, "");
|
|
7
7
|
export const INSTALLED_UPDATE_STATUS_TEXT = "Update installed, restart to update";
|
|
8
|
+
export const BLOCKED_UPDATE_STATUS_TEXT = "Update prepared; activation blocked";
|
|
8
9
|
/** Shared green restart notice for the pinned status and bare-A1 prompt overlay. */
|
|
9
10
|
export function renderPiShellInstalledUpdateStatus(width) {
|
|
10
11
|
if (width <= 0)
|
|
@@ -12,6 +13,13 @@ export function renderPiShellInstalledUpdateStatus(width) {
|
|
|
12
13
|
ensurePiTheme();
|
|
13
14
|
return piShellTruncateToWidth(piTheme().fg("success", INSTALLED_UPDATE_STATUS_TEXT), width);
|
|
14
15
|
}
|
|
16
|
+
/** Sanitized warning for a certified candidate that cannot yet preserve resident continuity. */
|
|
17
|
+
export function renderPiShellBlockedUpdateStatus(width) {
|
|
18
|
+
if (width <= 0)
|
|
19
|
+
return "";
|
|
20
|
+
ensurePiTheme();
|
|
21
|
+
return piShellTruncateToWidth(piTheme().fg("warning", BLOCKED_UPDATE_STATUS_TEXT), width);
|
|
22
|
+
}
|
|
15
23
|
/** A clickable URL in pinned Pi's grammar: an OSC 8 link where the terminal supports one, the plain URL otherwise. */
|
|
16
24
|
export const piShellHyperlink = (url, text = url) => hyperlink(url, text);
|
|
17
25
|
// Provenance: pinned from packages/coding-agent/src/core/slash-commands.ts at 914cf14.
|
|
@@ -217,9 +217,17 @@ export class PiEngineAdapter {
|
|
|
217
217
|
return;
|
|
218
218
|
}
|
|
219
219
|
if (release.status === "installed") {
|
|
220
|
+
this.#state.diagnosticsState.clearCodes(["release-update-installed", "release-update-blocked"]);
|
|
220
221
|
this.#addDiagnostic("info", "release-update-installed", `Update ${release.version} installed; restart to update.`, true);
|
|
221
222
|
}
|
|
222
|
-
else {
|
|
223
|
+
else if (release.status === "blocked") {
|
|
224
|
+
this.#state.diagnosticsState.clearCodes(["release-update-installed", "release-update-blocked"]);
|
|
225
|
+
this.#addDiagnostic("info", "release-update-blocked", `Update ${release.version} prepared; activation blocked.`, true);
|
|
226
|
+
}
|
|
227
|
+
else if (release.status === "blocked-cleared") {
|
|
228
|
+
this.#state.diagnosticsState.clearCodes(["release-update-blocked"]);
|
|
229
|
+
}
|
|
230
|
+
else if (release.status === undefined || release.status === "available") {
|
|
223
231
|
const changelog = release.changelogUrl === null ? "" : `\nChangelog: ${release.changelogUrl}`;
|
|
224
232
|
this.#addDiagnostic("info", "release-update", `New version ${release.version} is available. Run ${release.command}${changelog}`, true);
|
|
225
233
|
}
|
|
@@ -5,5 +5,6 @@ export declare class EngineDiagnostics {
|
|
|
5
5
|
get values(): readonly OwnedUiDiagnostics[];
|
|
6
6
|
get messages(): readonly string[];
|
|
7
7
|
record(severity: OwnedUiDiagnostics["severity"], code: string, message: string, recoverable: boolean): OwnedUiDiagnostics;
|
|
8
|
+
clearCodes(codes: readonly string[]): boolean;
|
|
8
9
|
acknowledgeClientVersion(version: string): boolean;
|
|
9
10
|
}
|
|
@@ -14,14 +14,27 @@ export class EngineDiagnostics {
|
|
|
14
14
|
this.#messages = [...this.#messages, message].slice(-8);
|
|
15
15
|
return diagnostic;
|
|
16
16
|
}
|
|
17
|
+
clearCodes(codes) {
|
|
18
|
+
const selected = new Set(codes);
|
|
19
|
+
return this.#remove(diagnostic => selected.has(diagnostic.code));
|
|
20
|
+
}
|
|
17
21
|
acknowledgeClientVersion(version) {
|
|
18
22
|
if (validSemver(version) === null)
|
|
19
23
|
return false;
|
|
24
|
+
return this.#remove(diagnostic => {
|
|
25
|
+
const target = diagnostic.code === "release-update-installed"
|
|
26
|
+
? /^Update (\S+) installed; restart to update\.$/u.exec(diagnostic.message)?.[1]
|
|
27
|
+
: diagnostic.code === "release-update-blocked"
|
|
28
|
+
? /^Update (\S+) prepared; activation blocked\.$/u.exec(diagnostic.message)?.[1]
|
|
29
|
+
: undefined;
|
|
30
|
+
return target !== undefined && validSemver(target) !== null && gte(version, target);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
#remove(predicate) {
|
|
20
34
|
const removed = new Set();
|
|
21
35
|
for (let index = this.#values.length - 1; index >= 0; index--) {
|
|
22
36
|
const diagnostic = this.#values[index];
|
|
23
|
-
|
|
24
|
-
if (installed !== undefined && validSemver(installed) !== null && gte(version, installed)) {
|
|
37
|
+
if (predicate(diagnostic)) {
|
|
25
38
|
removed.add(diagnostic.message);
|
|
26
39
|
this.#values.splice(index, 1);
|
|
27
40
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"platform": "darwin",
|
|
8
8
|
"architecture": "arm64",
|
|
9
9
|
"capability": "supported",
|
|
10
|
-
"builtAt": "2026-11-
|
|
10
|
+
"builtAt": "2026-11-03T12:34:41.201Z",
|
|
11
11
|
"artifact": {
|
|
12
12
|
"filename": "process-guardian.node",
|
|
13
13
|
"sha256": "a87eaaffcd986b624c8b044b756446cc1bdaf4334a1775b97820542fe34f9fb7",
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"architecture": "arm64",
|
|
10
10
|
"target": "aarch64-apple-darwin",
|
|
11
11
|
"capability": "supported",
|
|
12
|
-
"builtAt": "2026-11-
|
|
12
|
+
"builtAt": "2026-11-03T12:38:29.199Z",
|
|
13
13
|
"artifact": {
|
|
14
14
|
"filename": "terminal-host.node",
|
|
15
|
-
"sha256": "
|
|
16
|
-
"size":
|
|
15
|
+
"sha256": "f340571d5eef45a551e7c53f8c31d91dd75c948fe11c97bda0c23a2d9bbfa2a4",
|
|
16
|
+
"size": 6585248
|
|
17
17
|
},
|
|
18
18
|
"entry": {
|
|
19
19
|
"filename": "dist/foundation/resident-tabs/host-entry.js",
|
|
Binary file
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"platform": "darwin",
|
|
8
8
|
"architecture": "x64",
|
|
9
9
|
"capability": "supported",
|
|
10
|
-
"builtAt": "2026-11-
|
|
10
|
+
"builtAt": "2026-11-03T12:34:49.251Z",
|
|
11
11
|
"artifact": {
|
|
12
12
|
"filename": "process-guardian.node",
|
|
13
13
|
"sha256": "783a65f0de287a1c02cd5429dbe97bc8633f4cc8a7b92e3e961180b9bdcff814",
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"architecture": "x64",
|
|
10
10
|
"target": "x86_64-apple-darwin",
|
|
11
11
|
"capability": "supported",
|
|
12
|
-
"builtAt": "2026-11-
|
|
12
|
+
"builtAt": "2026-11-03T12:41:50.549Z",
|
|
13
13
|
"artifact": {
|
|
14
14
|
"filename": "terminal-host.node",
|
|
15
|
-
"sha256": "
|
|
16
|
-
"size":
|
|
15
|
+
"sha256": "92cceb0cf8a8b955b86762fc677396586f0e32569923827a96281fc5a29a3add",
|
|
16
|
+
"size": 7173680
|
|
17
17
|
},
|
|
18
18
|
"entry": {
|
|
19
19
|
"filename": "dist/foundation/resident-tabs/host-entry.js",
|
|
Binary file
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"platform": "linux",
|
|
8
8
|
"architecture": "x64",
|
|
9
9
|
"capability": "supported",
|
|
10
|
-
"builtAt": "2026-11-
|
|
10
|
+
"builtAt": "2026-11-03T12:33:48.123Z",
|
|
11
11
|
"artifact": {
|
|
12
12
|
"filename": "process-guardian.node",
|
|
13
13
|
"sha256": "118e5954e3864672bd8482ab7edf419cb27dcdf2533720316979b7f5cc5a53a5",
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"architecture": "x64",
|
|
10
10
|
"target": "x86_64-unknown-linux-gnu",
|
|
11
11
|
"capability": "supported",
|
|
12
|
-
"builtAt": "2026-11-
|
|
12
|
+
"builtAt": "2026-11-03T12:37:02.302Z",
|
|
13
13
|
"artifact": {
|
|
14
14
|
"filename": "terminal-host.node",
|
|
15
|
-
"sha256": "
|
|
16
|
-
"size":
|
|
15
|
+
"sha256": "e5d97e8f613e7ca960f541861f9ae61435fb5e1cff1a5e80e34000665e5afecf",
|
|
16
|
+
"size": 7954680
|
|
17
17
|
},
|
|
18
18
|
"entry": {
|
|
19
19
|
"filename": "dist/foundation/resident-tabs/host-entry.js",
|
|
Binary file
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
"platform": "win32",
|
|
8
8
|
"architecture": "x64",
|
|
9
9
|
"capability": "supported",
|
|
10
|
-
"builtAt": "2026-11-
|
|
10
|
+
"builtAt": "2026-11-03T12:34:57.755Z",
|
|
11
11
|
"artifact": {
|
|
12
12
|
"filename": "process-guardian.node",
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "037cc7426ed420a010993dcd7fda7001707fc87d0c6104f93c01e32c789bd748",
|
|
14
14
|
"size": 402432
|
|
15
15
|
},
|
|
16
16
|
"provenance": {
|
|
Binary file
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"architecture": "x64",
|
|
10
10
|
"target": "x86_64-pc-windows-msvc",
|
|
11
11
|
"capability": "supported",
|
|
12
|
-
"builtAt": "2026-11-
|
|
12
|
+
"builtAt": "2026-11-03T12:36:35.360Z",
|
|
13
13
|
"artifact": {
|
|
14
14
|
"filename": "terminal-host.node",
|
|
15
|
-
"sha256": "
|
|
16
|
-
"size":
|
|
15
|
+
"sha256": "2dd46801648d082efbf5c6122ed76a059a6fee052ca0364dea517ddb3f1f6ba9",
|
|
16
|
+
"size": 7071232
|
|
17
17
|
},
|
|
18
18
|
"entry": {
|
|
19
19
|
"filename": "dist/foundation/resident-tabs/host-entry.js",
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timurproko/a1",
|
|
3
|
-
"version": "0.2.7-dev.
|
|
3
|
+
"version": "0.2.7-dev.1236",
|
|
4
4
|
"description": "Standalone terminal workspace for supervised native and managed agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"privateLaunchContract": "neutral-launch-v1",
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"@mariozechner/clipboard": "^0.3.9"
|
|
124
124
|
},
|
|
125
125
|
"publication": {
|
|
126
|
-
"source": "
|
|
127
|
-
"pullRequest":
|
|
126
|
+
"source": "ebe579aafe67e84a7d77b1769d8e8a972d5a30dc",
|
|
127
|
+
"pullRequest": 861
|
|
128
128
|
}
|
|
129
129
|
}
|