@timurproko/a1 0.2.7-dev.792 → 0.2.7-dev.806
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/clipboard-diagnostics.js +1 -1
- package/dist/app/session-shell/response-copy-helper.js +21 -6
- package/dist/app/session-shell/response-copy-protocol.d.ts +1 -1
- package/dist/app/session-shell/response-copy-transport.d.ts +3 -6
- package/dist/app/session-shell/response-copy-transport.js +60 -17
- package/dist/app/session-shell/session-shell-root.d.ts +2 -0
- package/dist/app/session-shell/session-shell-root.js +6 -1
- package/dist/app/session-shell/session-shell.d.ts +1 -0
- package/dist/app/session-shell/session-shell.js +22 -19
- package/dist/cli/tabs.js +1 -1
- package/dist/composition/owned-ui.d.ts +1 -0
- package/dist/composition/owned-ui.js +16 -3
- package/dist/features/resident-tab/shortcut-declarations.d.ts +1 -1
- package/dist/features/resident-tab/shortcut-declarations.js +1 -0
- package/dist/foundation/launch-guardian/exit-watcher.d.ts +14 -0
- package/dist/foundation/launch-guardian/exit-watcher.js +94 -0
- package/dist/foundation/launch-guardian/main.d.ts +2 -1
- package/dist/foundation/launch-guardian/main.js +58 -6
- package/dist/foundation/process-containment/{native-guardian-containment.d.ts → addon-containment.d.ts} +7 -5
- package/dist/foundation/process-containment/addon-containment.js +139 -0
- package/dist/foundation/process-containment/addon-process-inspector.d.ts +11 -0
- package/dist/foundation/process-containment/addon-process-inspector.js +36 -0
- package/dist/foundation/process-containment/addon.d.ts +42 -0
- package/dist/foundation/process-containment/addon.js +57 -0
- package/dist/foundation/process-containment/artifact.d.ts +1 -1
- package/dist/foundation/process-containment/artifact.js +6 -5
- package/dist/foundation/process-containment/contracts.d.ts +2 -0
- package/dist/foundation/process-containment/index.d.ts +4 -4
- package/dist/foundation/process-containment/index.js +3 -3
- package/dist/foundation/process-containment/native-process-inspector.d.ts +2 -1
- package/dist/foundation/process-containment/native-process-inspector.js +10 -10
- package/dist/foundation/release/bootstrap.js +53 -16
- package/dist/foundation/release/index.d.ts +1 -1
- package/dist/foundation/release/index.js +1 -1
- package/dist/foundation/release/release-store.d.ts +12 -0
- package/dist/foundation/release/release-store.js +124 -2
- package/dist/foundation/resident-tabs/bounded-host-command.d.ts +3 -1
- package/dist/foundation/resident-tabs/bounded-host-command.js +6 -2
- package/dist/foundation/resident-tabs/bridge-client.d.ts +2 -0
- package/dist/foundation/resident-tabs/bridge-client.js +13 -0
- package/dist/foundation/resident-tabs/doctor.d.ts +0 -1
- package/dist/foundation/resident-tabs/doctor.js +4 -4
- package/dist/foundation/resident-tabs/ensure-terminal-host.d.ts +7 -2
- package/dist/foundation/resident-tabs/ensure-terminal-host.js +15 -11
- package/dist/foundation/resident-tabs/host-entry.d.ts +1 -0
- package/dist/foundation/resident-tabs/host-entry.js +17 -0
- package/dist/foundation/resident-tabs/index.d.ts +2 -2
- package/dist/foundation/resident-tabs/index.js +1 -1
- package/dist/foundation/resident-tabs/maintenance.d.ts +5 -4
- package/dist/foundation/resident-tabs/maintenance.js +32 -17
- package/dist/foundation/resident-tabs/release-holds.d.ts +3 -2
- package/dist/foundation/resident-tabs/release-holds.js +16 -7
- package/dist/foundation/resident-tabs/settings.d.ts +0 -1
- package/dist/foundation/resident-tabs/settings.js +1 -7
- package/dist/foundation/resident-tabs/terminal-host-artifact.d.ts +11 -15
- package/dist/foundation/resident-tabs/terminal-host-artifact.js +57 -32
- package/dist/foundation/session-writer-lock/index.d.ts +1 -3
- package/dist/foundation/session-writer-lock/index.js +8 -6
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +2 -1
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +2 -0
- package/dist/native/darwin-arm64/manifest.json +8 -6
- package/dist/native/darwin-arm64/process-guardian.node +0 -0
- package/dist/native/darwin-arm64/terminal-host-THIRD-PARTY-NOTICES.txt +1 -1
- package/dist/native/darwin-arm64/terminal-host.manifest.json +14 -13
- package/dist/native/darwin-arm64/terminal-host.node +0 -0
- package/dist/native/darwin-x64/manifest.json +9 -7
- package/dist/native/darwin-x64/process-guardian.node +0 -0
- package/dist/native/darwin-x64/terminal-host-THIRD-PARTY-NOTICES.txt +1 -1
- package/dist/native/darwin-x64/terminal-host.manifest.json +15 -14
- package/dist/native/darwin-x64/terminal-host.node +0 -0
- package/dist/native/linux-x64/manifest.json +8 -6
- package/dist/native/linux-x64/process-guardian.node +0 -0
- package/dist/native/linux-x64/terminal-host-THIRD-PARTY-NOTICES.txt +1 -1
- package/dist/native/linux-x64/terminal-host.manifest.json +14 -13
- package/dist/native/linux-x64/{terminal-host → terminal-host.node} +0 -0
- package/dist/native/win32-x64/manifest.json +8 -6
- package/dist/native/win32-x64/process-guardian.node +0 -0
- package/dist/native/win32-x64/terminal-host-THIRD-PARTY-NOTICES.txt +1 -1
- package/dist/native/win32-x64/terminal-host.manifest.json +14 -13
- package/dist/native/win32-x64/{terminal-host.exe → 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 +3 -2
- package/dist/runtime-payload-inventory.json +1 -0
- package/dist/ui/settings/declarations.d.ts +0 -12
- package/dist/ui/settings/declarations.js +0 -10
- package/docs/architecture/boundaries.md +10 -9
- package/docs/architecture/process-guardian-provenance.md +17 -11
- package/docs/architecture/resident-tabs.md +36 -34
- package/docs/architecture/terminal-host-provenance.md +5 -3
- package/docs/architecture/toolchain.md +8 -6
- package/docs/ci-release-runbook.md +1 -1
- package/docs/openspec-archive-automation.md +23 -9
- package/docs/validation.md +6 -2
- package/package.json +5 -3
- package/dist/foundation/process-containment/darwin-process-inspector.d.ts +0 -5
- package/dist/foundation/process-containment/darwin-process-inspector.js +0 -7
- package/dist/foundation/process-containment/native-guardian-containment.js +0 -152
- package/dist/foundation/process-containment/windows-process-inspector.d.ts +0 -19
- package/dist/foundation/process-containment/windows-process-inspector.js +0 -56
- package/dist/native/darwin-arm64/a1_session_lock.node +0 -0
- package/dist/native/darwin-arm64/process-guardian +0 -0
- package/dist/native/darwin-arm64/terminal-host +0 -0
- package/dist/native/darwin-x64/a1_session_lock.node +0 -0
- package/dist/native/darwin-x64/process-guardian +0 -0
- package/dist/native/darwin-x64/terminal-host +0 -0
- package/dist/native/linux-x64/a1_session_lock.node +0 -0
- package/dist/native/linux-x64/process-guardian +0 -0
- package/dist/native/win32-x64/a1_session_lock.node +0 -0
- package/dist/native/win32-x64/process-guardian.exe +0 -0
|
@@ -12,7 +12,7 @@ import { cleanupProvenIdleOwner, processIsAlive } from "./process-cleanup.js";
|
|
|
12
12
|
import { probeOwnership, readEndpointMetadata, removeEndpointArtifacts, sweepDeadEndpoints } from "./endpoints.js";
|
|
13
13
|
import { UpdateTransactionStore } from "./update-transaction.js";
|
|
14
14
|
import { selectUpdateLaunchRelease } from "./update-launch.js";
|
|
15
|
-
import { consumeMaterializationProof, materializeRelease, readCertifiedReleaseManifest, readMaterializedRelease, resolveReleaseEntryPoint, verifyMaterializedRelease } from "./release-store.js";
|
|
15
|
+
import { assertCertifiedFilesPresent, consumeMaterializationProof, materializeRelease, readCertifiedReleaseManifest, readMaterializedRelease, repairMaterializedRelease, resolveReleaseEntryPoint, verifyMaterializedRelease } from "./release-store.js";
|
|
16
16
|
import { scheduleReleaseCleanup } from "./release-gc.js";
|
|
17
17
|
import { createRestartSeal, readRestartCertifiedRelease, releaseCertificationDocument } from "./restart-certification.js";
|
|
18
18
|
import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
|
|
@@ -61,10 +61,10 @@ async function runBootstrapAttempt(options, retriesRemaining) {
|
|
|
61
61
|
});
|
|
62
62
|
if (resident !== null) {
|
|
63
63
|
residentContext = resident.status === "ready" ? {
|
|
64
|
-
residentRootExecutable: resident.executable,
|
|
64
|
+
residentRootExecutable: resident.executable.executable,
|
|
65
65
|
residentRootArguments: JSON.stringify([
|
|
66
|
+
resident.executable.entry,
|
|
66
67
|
"attach", "--endpoint", resident.endpoint, "--cwd", process.cwd(), "--clear-exit-ms", String(CLEAR_EXIT_INTERVAL_MS),
|
|
67
|
-
"--tabs-max", String((await residentLaunchSettings).tabsMax),
|
|
68
68
|
"--profile", environment[PRODUCT_IDENTITY.environment.profileHome] ?? paths.configDir,
|
|
69
69
|
"--settings-file", resolve(agentProfileDir, "settings.json"),
|
|
70
70
|
"--data-dir", paths.dataDir, "--runtime-dir", paths.runtimeDir,
|
|
@@ -126,7 +126,23 @@ async function runBootstrapAttempt(options, retriesRemaining) {
|
|
|
126
126
|
const installedVersion = await readInstalledVersion(options.packageRoot);
|
|
127
127
|
const activeId = state.references.active;
|
|
128
128
|
const active = activeId === null ? undefined : state.releases[activeId];
|
|
129
|
-
|
|
129
|
+
let activeIsLaunchable = active === undefined || (options.releaseIsLaunchable?.(active.releaseRoot) ?? true);
|
|
130
|
+
let excludedRetainedReleaseId;
|
|
131
|
+
if (activeIsLaunchable && active?.approval === "approved" && active.packageVersion === installedVersion) {
|
|
132
|
+
try {
|
|
133
|
+
await assertCertifiedFilesPresent(active.releaseRoot);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
try {
|
|
137
|
+
await repairApprovedRelease(options.packageRoot, active, paths.dataDir);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Security: a damaged certified release is no longer a fallback when package bytes cannot restore it.
|
|
141
|
+
activeIsLaunchable = false;
|
|
142
|
+
excludedRetainedReleaseId = active.releaseId;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
130
146
|
if (activeIsLaunchable && active?.approval === "approved" && active.packageVersion === installedVersion) {
|
|
131
147
|
const endpointMatches = endpoint?.releaseId === active.releaseId
|
|
132
148
|
&& endpoint.releaseRoot === active.releaseRoot
|
|
@@ -140,18 +156,27 @@ async function runBootstrapAttempt(options, retriesRemaining) {
|
|
|
140
156
|
await removeEndpointArtifacts(endpointPaths.endpointMetadataPath, endpointPaths.endpoint);
|
|
141
157
|
await markStartupPhase(environment, "durable-validation-start");
|
|
142
158
|
const retained = await readRestartCertifiedRelease(active, paths.dataDir).catch(async () => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
159
|
+
let verified;
|
|
160
|
+
try {
|
|
161
|
+
verified = await readMaterializedRelease(active.releaseRoot, resolve(paths.dataDir, "releases"));
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
verified = await repairApprovedRelease(options.packageRoot, active, paths.dataDir);
|
|
165
|
+
return verified;
|
|
146
166
|
}
|
|
167
|
+
assertApprovedReleaseMatches(verified, active);
|
|
147
168
|
await certifyMaterializedRelease(verified, paths.dataDir);
|
|
148
169
|
return verified;
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
170
|
+
}).catch(() => null);
|
|
171
|
+
if (retained !== null) {
|
|
172
|
+
await markStartupPhase(environment, "durable-validation-complete");
|
|
173
|
+
await markStartupPhase(environment, "replacement-supervisor-start");
|
|
174
|
+
await ensureSupervisor(retained, environment);
|
|
175
|
+
await markStartupPhase(environment, "replacement-supervisor-ready");
|
|
176
|
+
return await launchSelected(retained);
|
|
177
|
+
}
|
|
178
|
+
activeIsLaunchable = false;
|
|
179
|
+
excludedRetainedReleaseId = active.releaseId;
|
|
155
180
|
}
|
|
156
181
|
}
|
|
157
182
|
// Concurrency: an installation being replaced is not a reason to refuse a launch. While
|
|
@@ -163,7 +188,7 @@ async function runBootstrapAttempt(options, retriesRemaining) {
|
|
|
163
188
|
candidate = await materializeRelease(options.packageRoot, paths.dataDir);
|
|
164
189
|
}
|
|
165
190
|
catch (error) {
|
|
166
|
-
const fallback = await launchRetainedActive(state, paths, environment, launchSelected);
|
|
191
|
+
const fallback = await launchRetainedActive(state, paths, environment, launchSelected, excludedRetainedReleaseId);
|
|
167
192
|
if (fallback === null)
|
|
168
193
|
throw error;
|
|
169
194
|
return fallback;
|
|
@@ -290,10 +315,10 @@ async function runBootstrapAttempt(options, retriesRemaining) {
|
|
|
290
315
|
* certifying anything new, and reports no selection when there is nothing safe
|
|
291
316
|
* to fall back to.
|
|
292
317
|
*/
|
|
293
|
-
async function launchRetainedActive(state, paths, environment, launch) {
|
|
318
|
+
async function launchRetainedActive(state, paths, environment, launch, excludedReleaseId) {
|
|
294
319
|
const activeId = state.references.active;
|
|
295
320
|
const active = activeId === null ? undefined : state.releases[activeId];
|
|
296
|
-
if (!active || active.approval !== "approved")
|
|
321
|
+
if (!active || active.approval !== "approved" || active.releaseId === excludedReleaseId)
|
|
297
322
|
return null;
|
|
298
323
|
try {
|
|
299
324
|
const retained = await readCertifiedReleaseManifest(active, resolve(paths.dataDir, "releases"));
|
|
@@ -304,6 +329,18 @@ async function launchRetainedActive(state, paths, environment, launch) {
|
|
|
304
329
|
return null;
|
|
305
330
|
}
|
|
306
331
|
}
|
|
332
|
+
async function repairApprovedRelease(packageRoot, active, dataDir) {
|
|
333
|
+
const repaired = await repairMaterializedRelease(packageRoot, active.releaseRoot);
|
|
334
|
+
assertApprovedReleaseMatches(repaired, active);
|
|
335
|
+
await recordParentCertifiedRelease(repaired, dataDir);
|
|
336
|
+
return repaired;
|
|
337
|
+
}
|
|
338
|
+
function assertApprovedReleaseMatches(release, active) {
|
|
339
|
+
if (release.releaseId !== active.releaseId || release.contentDigest !== active.contentDigest
|
|
340
|
+
|| (active.packageVersion !== undefined && release.packageVersion !== active.packageVersion)) {
|
|
341
|
+
throw new Error("completely verified release differs from the approved active record");
|
|
342
|
+
}
|
|
343
|
+
}
|
|
307
344
|
async function readInstalledVersion(packageRoot) {
|
|
308
345
|
const manifest = JSON.parse(await readFile(resolve(packageRoot, "package.json"), "utf8"));
|
|
309
346
|
if (typeof manifest.version !== "string" || manifest.version.length === 0) {
|
|
@@ -15,7 +15,7 @@ export { PRODUCT_PACKAGE_NAME, createReleaseIdentity, deriveReleaseIdentity, dig
|
|
|
15
15
|
export type { DiscoverReleasePayloadOptions, DiscoveredReleasePayload, ReleaseFileIdentity, ReleaseIdentity } from "./release.js";
|
|
16
16
|
export { collectRelease, prepareReleaseCleanup, runBoundedReleaseCleanup, runReleaseCleanupWorker, scheduleReleaseCleanup } from "./release-gc.js";
|
|
17
17
|
export type { ReleaseCleanupLimits, ReleaseCleanupOperations, ReleaseCleanupOptions, ReleaseCleanupResult, ReleaseCleanupWorkerOptions } from "./release-gc.js";
|
|
18
|
-
export { RELEASE_MANIFEST_FILENAME, assertImmutableExecutionRoot, consumeMaterializationProof, materializeRelease, readCertifiedReleaseManifest, readMaterializedRelease, resolveReleaseEntryPoint, verifyMaterializedRelease, } from "./release-store.js";
|
|
18
|
+
export { RELEASE_MANIFEST_FILENAME, ReleaseRepairUnavailableError, assertCertifiedFilesPresent, assertImmutableExecutionRoot, consumeMaterializationProof, materializeRelease, readCertifiedReleaseManifest, readMaterializedRelease, repairMaterializedRelease, resolveReleaseEntryPoint, verifyMaterializedRelease, } from "./release-store.js";
|
|
19
19
|
export type { CertifiedReleaseRecord, MaterializeReleaseOptions, MaterializedRelease, ReadCertifiedReleaseManifestOptions, ReleaseContentOperation, ReleaseContentOperationEvent, VerifyMaterializedReleaseOptions, } from "./release-store.js";
|
|
20
20
|
export { createRestartSeal, readRestartCertifiedRelease, releaseCertificationDocument, restartSealDigest } from "./restart-certification.js";
|
|
21
21
|
export type { RestartSeal, RestartValidationEvent } from "./restart-certification.js";
|
|
@@ -7,7 +7,7 @@ export { listRecordedEndpoints, liveReleaseIds, probeOwnership, readEndpointMeta
|
|
|
7
7
|
export { cleanupProvenIdleOwner, cleanupVerifiedOwner, processIsAlive } from "./process-cleanup.js";
|
|
8
8
|
export { PRODUCT_PACKAGE_NAME, createReleaseIdentity, deriveReleaseIdentity, digestManifestFiles, discoverReleasePayload, packageRootFromModule, releaseFileIdentity, resolveWithin, } from "./release.js";
|
|
9
9
|
export { collectRelease, prepareReleaseCleanup, runBoundedReleaseCleanup, runReleaseCleanupWorker, scheduleReleaseCleanup } from "./release-gc.js";
|
|
10
|
-
export { RELEASE_MANIFEST_FILENAME, assertImmutableExecutionRoot, consumeMaterializationProof, materializeRelease, readCertifiedReleaseManifest, readMaterializedRelease, resolveReleaseEntryPoint, verifyMaterializedRelease, } from "./release-store.js";
|
|
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
13
|
export { PACKAGE_UNLOCK_PATIENCE_MS, PRODUCT_PACKAGE, assertUpdatePerformanceBudget, createNpmProcessRunner, createUpdateLifecycleCoordinator, isPackageLockError, lockedPackageDiagnostic, planUpdateOwnership, renderUpdateProgressBar, runSelfUpdate, } from "./update.js";
|
|
@@ -31,12 +31,24 @@ export interface CertifiedReleaseRecord {
|
|
|
31
31
|
/** Absent on records written before releases declared a handoff contract. */
|
|
32
32
|
readonly launchContract?: string;
|
|
33
33
|
}
|
|
34
|
+
/** Reports that installed package bytes cannot safely reproduce an approved release. */
|
|
35
|
+
export declare class ReleaseRepairUnavailableError extends Error {
|
|
36
|
+
readonly code: "RELEASE_REPAIR_UNAVAILABLE";
|
|
37
|
+
constructor(message: string, options?: ErrorOptions);
|
|
38
|
+
}
|
|
34
39
|
/** Process-authority compatibility controls for metadata-only release loading. */
|
|
35
40
|
export type ReadCertifiedReleaseManifestOptions = ReadCertifiedDependencyLayerOptions;
|
|
36
41
|
export declare function materializeRelease(packageRoot: string, dataDir: string, options?: MaterializeReleaseOptions): Promise<MaterializedRelease>;
|
|
37
42
|
/** Consume proof that this exact object was freshly materialized or fully verified in this process. */
|
|
38
43
|
export declare function consumeMaterializationProof(release: MaterializedRelease): boolean;
|
|
39
44
|
export declare function readMaterializedRelease(releaseRoot: string, selectedStoreRoot?: string): Promise<MaterializedRelease>;
|
|
45
|
+
/** Fast presence check before trusted certification permits a release to skip full verification. */
|
|
46
|
+
export declare function assertCertifiedFilesPresent(releaseRoot: string): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Restore changed or missing product payload files from the installed package without replacing the
|
|
49
|
+
* immutable release directory. Dependency layers retain their own verification and repair policy.
|
|
50
|
+
*/
|
|
51
|
+
export declare function repairMaterializedRelease(packageRoot: string, releaseRoot: string): Promise<MaterializedRelease>;
|
|
40
52
|
/**
|
|
41
53
|
* Load metadata for a release whose bytes were already certified by the
|
|
42
54
|
* current parent process or an authenticated live supervisor. Callers must
|
|
@@ -10,6 +10,14 @@ import { dependencyReference, materializeDependencyLayer, readCertifiedDependenc
|
|
|
10
10
|
const RELEASE_FILE_IO_CONCURRENCY = 32;
|
|
11
11
|
const certificationReady = new WeakSet();
|
|
12
12
|
export const RELEASE_MANIFEST_FILENAME = PRODUCT_IDENTITY.manifest.releaseFilename;
|
|
13
|
+
/** Reports that installed package bytes cannot safely reproduce an approved release. */
|
|
14
|
+
export class ReleaseRepairUnavailableError extends Error {
|
|
15
|
+
code = "RELEASE_REPAIR_UNAVAILABLE";
|
|
16
|
+
constructor(message, options = {}) {
|
|
17
|
+
super(message, options);
|
|
18
|
+
this.name = "ReleaseRepairUnavailableError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
13
21
|
export async function materializeRelease(packageRoot, dataDir, options = {}) {
|
|
14
22
|
const payload = await discoverReleasePayload(packageRoot, {
|
|
15
23
|
onSourceRead: (path, bytes) => options.onOperation?.({ operation: "source-read", path, bytes }),
|
|
@@ -71,7 +79,7 @@ export async function materializeRelease(packageRoot, dataDir, options = {}) {
|
|
|
71
79
|
const releaseRoot = resolveWithin(storeRoot, identity.releaseId);
|
|
72
80
|
if (await lstat(releaseRoot).catch(() => null)) {
|
|
73
81
|
await rm(candidate, { recursive: true, force: true });
|
|
74
|
-
return certificationReadyRelease(await
|
|
82
|
+
return certificationReadyRelease(await verifyOrRepairMaterializedRelease(payload.packageRoot, releaseRoot, identity));
|
|
75
83
|
}
|
|
76
84
|
await writeFile(resolve(candidate, RELEASE_MANIFEST_FILENAME), JSON.stringify(identity, null, 2), { mode: 0o400, flag: "wx" });
|
|
77
85
|
try {
|
|
@@ -81,7 +89,7 @@ export async function materializeRelease(packageRoot, dataDir, options = {}) {
|
|
|
81
89
|
if (!await lstat(releaseRoot).catch(() => null))
|
|
82
90
|
throw error;
|
|
83
91
|
await rm(candidate, { recursive: true, force: true });
|
|
84
|
-
return certificationReadyRelease(await
|
|
92
|
+
return certificationReadyRelease(await verifyOrRepairMaterializedRelease(payload.packageRoot, releaseRoot, identity));
|
|
85
93
|
}
|
|
86
94
|
return certificationReadyRelease({ ...identity, releaseRoot: await realpath(releaseRoot) });
|
|
87
95
|
}
|
|
@@ -102,6 +110,61 @@ export async function readMaterializedRelease(releaseRoot, selectedStoreRoot) {
|
|
|
102
110
|
const manifest = JSON.parse(await readFile(resolve(canonical, RELEASE_MANIFEST_FILENAME), "utf8"));
|
|
103
111
|
return certificationReadyRelease(await verifyMaterializedRelease(canonical, manifest, selectedStoreRoot));
|
|
104
112
|
}
|
|
113
|
+
/** Fast presence check before trusted certification permits a release to skip full verification. */
|
|
114
|
+
export async function assertCertifiedFilesPresent(releaseRoot) {
|
|
115
|
+
const canonical = await realpath(releaseRoot);
|
|
116
|
+
const manifest = await readReleaseManifest(canonical);
|
|
117
|
+
await mapWithConcurrency(manifest.files, RELEASE_FILE_IO_CONCURRENCY, async (file) => {
|
|
118
|
+
const metadata = await lstat(resolveWithin(canonical, file.path));
|
|
119
|
+
if (!metadata.isFile() || metadata.isSymbolicLink())
|
|
120
|
+
throw new Error(`certified release file is missing: ${file.path}`);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Restore changed or missing product payload files from the installed package without replacing the
|
|
125
|
+
* immutable release directory. Dependency layers retain their own verification and repair policy.
|
|
126
|
+
*/
|
|
127
|
+
export async function repairMaterializedRelease(packageRoot, releaseRoot) {
|
|
128
|
+
const canonical = await realpath(releaseRoot);
|
|
129
|
+
let manifest;
|
|
130
|
+
let payload;
|
|
131
|
+
try {
|
|
132
|
+
manifest = await readReleaseManifest(canonical);
|
|
133
|
+
payload = await discoverReleasePayload(packageRoot);
|
|
134
|
+
assertCurrentLaunchContract(payload);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
throw repairUnavailable("release repair metadata is unavailable", error);
|
|
138
|
+
}
|
|
139
|
+
if (payload.packageVersion !== manifest.packageVersion) {
|
|
140
|
+
throw repairUnavailable(`installed package version ${payload.packageVersion} does not match release ${manifest.packageVersion}`);
|
|
141
|
+
}
|
|
142
|
+
const payloadPaths = new Set(payload.paths.filter(path => !path.startsWith("node_modules/")));
|
|
143
|
+
try {
|
|
144
|
+
await mapWithConcurrency(manifest.files, RELEASE_FILE_IO_CONCURRENCY, async (file) => {
|
|
145
|
+
const destination = resolveWithin(canonical, file.path);
|
|
146
|
+
if (!await certifiedFileDiffers(destination, file))
|
|
147
|
+
return;
|
|
148
|
+
if (!payloadPaths.has(file.path))
|
|
149
|
+
throw repairUnavailable(`installed package does not carry ${file.path}`);
|
|
150
|
+
const source = resolveWithin(payload.packageRoot, file.path);
|
|
151
|
+
const sourceMetadata = await lstat(source);
|
|
152
|
+
if (!sourceMetadata.isFile() || sourceMetadata.isSymbolicLink())
|
|
153
|
+
throw repairUnavailable(`installed package file is not regular: ${file.path}`);
|
|
154
|
+
const bytes = payload.cachedFiles.get(file.path) ?? await readFile(source);
|
|
155
|
+
if (bytes.length !== file.bytes || createHash("sha256").update(bytes).digest("hex") !== file.sha256) {
|
|
156
|
+
throw repairUnavailable(`installed package bytes do not match release file ${file.path}`);
|
|
157
|
+
}
|
|
158
|
+
await replaceReleaseFile(destination, bytes, file);
|
|
159
|
+
});
|
|
160
|
+
return await verifyMaterializedRelease(canonical, manifest);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
if (error instanceof ReleaseRepairUnavailableError)
|
|
164
|
+
throw error;
|
|
165
|
+
throw repairUnavailable("release repair could not restore the certified payload", error);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
105
168
|
/**
|
|
106
169
|
* Load metadata for a release whose bytes were already certified by the
|
|
107
170
|
* current parent process or an authenticated live supervisor. Callers must
|
|
@@ -197,6 +260,65 @@ async function verifyFile(root, file, options) {
|
|
|
197
260
|
if (digest !== file.sha256)
|
|
198
261
|
throw new Error(`release file digest mismatch: ${file.path}`);
|
|
199
262
|
}
|
|
263
|
+
async function verifyOrRepairMaterializedRelease(packageRoot, releaseRoot, expected) {
|
|
264
|
+
try {
|
|
265
|
+
return await verifyMaterializedRelease(releaseRoot, expected);
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
await repairMaterializedRelease(packageRoot, releaseRoot);
|
|
269
|
+
return await verifyMaterializedRelease(releaseRoot, expected);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
async function readReleaseManifest(releaseRoot) {
|
|
273
|
+
const manifest = JSON.parse(await readFile(resolveWithin(releaseRoot, RELEASE_MANIFEST_FILENAME), "utf8"));
|
|
274
|
+
validateManifest(manifest);
|
|
275
|
+
if (releaseRoot.split(sep).at(-1) !== manifest.releaseId)
|
|
276
|
+
throw new Error(`release directory does not match identity ${manifest.releaseId}`);
|
|
277
|
+
return manifest;
|
|
278
|
+
}
|
|
279
|
+
async function certifiedFileDiffers(path, file) {
|
|
280
|
+
const metadata = await lstat(path).catch((error) => {
|
|
281
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
282
|
+
return null;
|
|
283
|
+
throw error;
|
|
284
|
+
});
|
|
285
|
+
if (metadata === null || !metadata.isFile() || metadata.isSymbolicLink() || metadata.size !== file.bytes)
|
|
286
|
+
return true;
|
|
287
|
+
if ((metadata.mode & 0o222) !== 0 || ((metadata.mode & 0o111) !== 0) !== file.executable)
|
|
288
|
+
return true;
|
|
289
|
+
return createHash("sha256").update(await readFile(path)).digest("hex") !== file.sha256;
|
|
290
|
+
}
|
|
291
|
+
async function replaceReleaseFile(path, bytes, file) {
|
|
292
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
293
|
+
const temporary = resolve(dirname(path), `.a1-repair-${randomUUID()}`);
|
|
294
|
+
const mode = file.executable ? 0o500 : 0o400;
|
|
295
|
+
let existing = null;
|
|
296
|
+
try {
|
|
297
|
+
await writeFile(temporary, bytes, { flag: "wx", mode: 0o600 });
|
|
298
|
+
existing = await lstat(path).catch((error) => {
|
|
299
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
300
|
+
return null;
|
|
301
|
+
throw error;
|
|
302
|
+
});
|
|
303
|
+
if (existing !== null) {
|
|
304
|
+
if (!existing.isFile() || existing.isSymbolicLink())
|
|
305
|
+
throw repairUnavailable(`release file cannot be safely replaced: ${file.path}`);
|
|
306
|
+
// Platform: Windows refuses rename-over for a read-only destination.
|
|
307
|
+
await chmod(path, 0o600);
|
|
308
|
+
}
|
|
309
|
+
await rename(temporary, path);
|
|
310
|
+
await chmod(path, mode);
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
await rm(temporary, { force: true }).catch(() => { });
|
|
314
|
+
if (existing !== null)
|
|
315
|
+
await chmod(path, mode).catch(() => { });
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function repairUnavailable(message, cause) {
|
|
320
|
+
return new ReleaseRepairUnavailableError(message, cause === undefined ? {} : { cause });
|
|
321
|
+
}
|
|
200
322
|
function certificationReadyRelease(release) {
|
|
201
323
|
certificationReady.add(release);
|
|
202
324
|
return release;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TerminalHostCommand } from "./ensure-terminal-host.js";
|
|
1
2
|
export interface BoundedHostCommandResult {
|
|
2
3
|
readonly code: number;
|
|
3
4
|
readonly stdout: string;
|
|
@@ -8,9 +9,10 @@ export interface BoundedHostCommandLimits {
|
|
|
8
9
|
readonly timeoutMs: number;
|
|
9
10
|
/** Names the exchange in the timeout error. */
|
|
10
11
|
readonly label: string;
|
|
12
|
+
readonly environment?: NodeJS.ProcessEnv;
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* Runs one terminal-host command whose report is small. An oversized report kills the command and
|
|
14
16
|
* reports a failed exchange; a command that outlives its deadline is killed and rejected.
|
|
15
17
|
*/
|
|
16
|
-
export declare function runBoundedHostCommand(
|
|
18
|
+
export declare function runBoundedHostCommand(command: TerminalHostCommand, arguments_: readonly string[], limits: BoundedHostCommandLimits): Promise<BoundedHostCommandResult>;
|
|
@@ -3,9 +3,13 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
* Runs one terminal-host command whose report is small. An oversized report kills the command and
|
|
4
4
|
* reports a failed exchange; a command that outlives its deadline is killed and rejected.
|
|
5
5
|
*/
|
|
6
|
-
export async function runBoundedHostCommand(
|
|
6
|
+
export async function runBoundedHostCommand(command, arguments_, limits) {
|
|
7
7
|
return await new Promise((resolvePromise, rejectPromise) => {
|
|
8
|
-
const child = spawn(executable, [...arguments_], {
|
|
8
|
+
const child = spawn(command.executable, [command.entry, ...arguments_], {
|
|
9
|
+
env: limits.environment,
|
|
10
|
+
windowsHide: true,
|
|
11
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
12
|
+
});
|
|
9
13
|
const stdout = [];
|
|
10
14
|
let size = 0;
|
|
11
15
|
let overflowed = false;
|
|
@@ -42,6 +42,7 @@ export interface TabBridgeOptions {
|
|
|
42
42
|
export declare class TabBridgeClient {
|
|
43
43
|
#private;
|
|
44
44
|
constructor(options: TabBridgeOptions);
|
|
45
|
+
get tabId(): string;
|
|
45
46
|
get available(): boolean;
|
|
46
47
|
captureAttachment(): TabAttachment | null;
|
|
47
48
|
onCommand(listener: (command: TabBridgeCommand) => void): () => void;
|
|
@@ -55,6 +56,7 @@ export declare class TabBridgeClient {
|
|
|
55
56
|
shortcuts(assignments: Readonly<Record<string, string | null>>): void;
|
|
56
57
|
requestDetach(origin?: TabAttachment | null, timeoutMs?: number): Promise<boolean>;
|
|
57
58
|
requestRestart(origin?: TabAttachment | null, timeoutMs?: number): Promise<boolean>;
|
|
59
|
+
requestClose(origin?: TabAttachment | null, timeoutMs?: number): Promise<boolean>;
|
|
58
60
|
consumeControllerBoundaryInput(data: string): string;
|
|
59
61
|
close(): void;
|
|
60
62
|
}
|
|
@@ -20,12 +20,16 @@ export class TabBridgeClient {
|
|
|
20
20
|
#pending = new Map();
|
|
21
21
|
#startWaiter = null;
|
|
22
22
|
#commandListeners = new Set();
|
|
23
|
+
#pendingCommands = [];
|
|
23
24
|
#boundaryInputRemainder = "";
|
|
24
25
|
constructor(options) { this.#options = options; }
|
|
26
|
+
get tabId() { return this.#options.credentials.tabId; }
|
|
25
27
|
get available() { return this.#available && this.#connected; }
|
|
26
28
|
captureAttachment() { return this.#attachment === null ? null : { ...this.#attachment }; }
|
|
27
29
|
onCommand(listener) {
|
|
28
30
|
this.#commandListeners.add(listener);
|
|
31
|
+
for (const command of this.#pendingCommands.splice(0))
|
|
32
|
+
listener(command);
|
|
29
33
|
return () => this.#commandListeners.delete(listener);
|
|
30
34
|
}
|
|
31
35
|
async start(timeoutMs = 4_000) {
|
|
@@ -119,6 +123,9 @@ export class TabBridgeClient {
|
|
|
119
123
|
requestRestart(origin = this.captureAttachment(), timeoutMs = 1_000) {
|
|
120
124
|
return this.#requestAction("restart", origin, timeoutMs);
|
|
121
125
|
}
|
|
126
|
+
requestClose(origin = this.captureAttachment(), timeoutMs = 1_000) {
|
|
127
|
+
return this.#requestAction("close", origin, timeoutMs);
|
|
128
|
+
}
|
|
122
129
|
consumeControllerBoundaryInput(data) {
|
|
123
130
|
const prefix = "\x1bP+a1;controller=";
|
|
124
131
|
const suffix = "\x1b\\";
|
|
@@ -176,6 +183,12 @@ export class TabBridgeClient {
|
|
|
176
183
|
this.#fail(false);
|
|
177
184
|
}
|
|
178
185
|
#emitCommand(command) {
|
|
186
|
+
if (this.#commandListeners.size === 0) {
|
|
187
|
+
if (this.#pendingCommands.length >= 16)
|
|
188
|
+
this.#pendingCommands.shift();
|
|
189
|
+
this.#pendingCommands.push(command);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
179
192
|
for (const listener of this.#commandListeners)
|
|
180
193
|
listener(command);
|
|
181
194
|
}
|
|
@@ -33,7 +33,7 @@ export async function writeDoctorBundle(options) {
|
|
|
33
33
|
node: process.version,
|
|
34
34
|
detachmentMode: options.hostStatus.result?.detachmentMode ?? null,
|
|
35
35
|
}),
|
|
36
|
-
json("settings.json", {
|
|
36
|
+
json("settings.json", { tabsAutoName: options.settings.tabsAutoName }),
|
|
37
37
|
];
|
|
38
38
|
const crashes = [];
|
|
39
39
|
for (const name of names.filter(name => CRASH_NAME.test(name)).sort()) {
|
|
@@ -153,11 +153,11 @@ async function redactedMarker(path, redact) {
|
|
|
153
153
|
async function artifactFacts(roots, platform, architecture, redact) {
|
|
154
154
|
const facts = [];
|
|
155
155
|
for (const root of roots) {
|
|
156
|
-
const
|
|
157
|
-
const manifest = await readFile(resolve(dirname(
|
|
156
|
+
const addon = resolveTerminalHostPath(root, {}, platform, architecture);
|
|
157
|
+
const manifest = await readFile(resolve(dirname(addon), TERMINAL_HOST_MANIFEST_FILENAME), "utf8")
|
|
158
158
|
.then(text => JSON.parse(text))
|
|
159
159
|
.catch(() => null);
|
|
160
|
-
const verification = await verifyTerminalHostArtifact(
|
|
160
|
+
const verification = await verifyTerminalHostArtifact(addon, platform, architecture)
|
|
161
161
|
.then(() => "verified")
|
|
162
162
|
.catch((error) => error instanceof Error && "code" in error ? String(error.code) : "failed");
|
|
163
163
|
facts.push({ releaseRoot: redact(root), verification, manifest });
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
export type TerminalHostFallbackReason = "unsupported platform" | "no interactive terminal" | "terminal host not installed" | "terminal host not verified" | "host probe failed" | "incompatible generation" | "registry unreadable" | "recovery blocked" | "launch not verified" | "host startup timed out";
|
|
2
|
+
export interface TerminalHostCommand {
|
|
3
|
+
readonly executable: string;
|
|
4
|
+
readonly entry: string;
|
|
5
|
+
}
|
|
2
6
|
export type EnsureTerminalHostResult = {
|
|
3
7
|
readonly status: "ready";
|
|
4
|
-
readonly executable:
|
|
8
|
+
readonly executable: TerminalHostCommand;
|
|
5
9
|
readonly endpoint: string;
|
|
6
10
|
} | {
|
|
7
11
|
readonly status: "fallback";
|
|
@@ -24,8 +28,9 @@ export interface EnsureTerminalHostOptions {
|
|
|
24
28
|
readonly maxConcurrentStarts?: number;
|
|
25
29
|
readonly unresponsiveRestartSeconds?: number;
|
|
26
30
|
readonly run?: (executable: string, arguments_: readonly string[], environment: NodeJS.ProcessEnv, timeoutMs: number) => Promise<HostCommandResult>;
|
|
27
|
-
readonly verify?: (
|
|
31
|
+
readonly verify?: (addon: string, platform: NodeJS.Platform, architecture: string) => Promise<unknown>;
|
|
28
32
|
}
|
|
29
33
|
export declare function resolveTerminalHostPath(releaseRoot: string, environment?: NodeJS.ProcessEnv, platform?: NodeJS.Platform, architecture?: string): string;
|
|
34
|
+
export declare function resolveTerminalHostEntryPath(releaseRoot: string): string;
|
|
30
35
|
export declare function ensureTerminalHost(options: EnsureTerminalHostOptions): Promise<EnsureTerminalHostResult>;
|
|
31
36
|
export {};
|
|
@@ -8,8 +8,10 @@ export function resolveTerminalHostPath(releaseRoot, environment = process.env,
|
|
|
8
8
|
const override = developmentOverride(environment);
|
|
9
9
|
if (override)
|
|
10
10
|
return resolve(override);
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
return resolve(releaseRoot, "dist", "native", `${platform}-${architecture}`, `${PRODUCT_IDENTITY.artifacts.terminalHostAddon}.node`);
|
|
12
|
+
}
|
|
13
|
+
export function resolveTerminalHostEntryPath(releaseRoot) {
|
|
14
|
+
return resolve(releaseRoot, "dist", "foundation", "resident-tabs", `${PRODUCT_IDENTITY.artifacts.terminalHostEntry}.js`);
|
|
13
15
|
}
|
|
14
16
|
/** The terminal-host override is a development aid; installed releases always run their own verified artifact. */
|
|
15
17
|
function developmentOverride(environment) {
|
|
@@ -22,10 +24,11 @@ export async function ensureTerminalHost(options) {
|
|
|
22
24
|
const architecture = options.architecture ?? process.arch;
|
|
23
25
|
if (!supported(platform, architecture))
|
|
24
26
|
return { status: "fallback", reason: "unsupported platform" };
|
|
25
|
-
const
|
|
27
|
+
const addon = resolveTerminalHostPath(options.releaseRoot, options.environment, platform, architecture);
|
|
28
|
+
const command = { executable: process.execPath, entry: resolveTerminalHostEntryPath(options.releaseRoot) };
|
|
26
29
|
if (developmentOverride(options.environment)) {
|
|
27
30
|
try {
|
|
28
|
-
await access(
|
|
31
|
+
await Promise.all([access(addon, constants.R_OK), access(command.entry, constants.R_OK)]);
|
|
29
32
|
}
|
|
30
33
|
catch {
|
|
31
34
|
return { status: "fallback", reason: "terminal host not installed" };
|
|
@@ -33,7 +36,7 @@ export async function ensureTerminalHost(options) {
|
|
|
33
36
|
}
|
|
34
37
|
else {
|
|
35
38
|
try {
|
|
36
|
-
await (options.verify ?? verifyTerminalHostArtifact)(
|
|
39
|
+
await (options.verify ?? verifyTerminalHostArtifact)(addon, platform, architecture);
|
|
37
40
|
}
|
|
38
41
|
catch (error) {
|
|
39
42
|
return { status: "fallback", reason: verificationReason(error) };
|
|
@@ -43,12 +46,13 @@ export async function ensureTerminalHost(options) {
|
|
|
43
46
|
const timeoutMs = options.timeoutMs ?? 8_000;
|
|
44
47
|
const common = ["--profile", options.profileRoot, "--data-dir", options.dataDir, "--runtime-dir", options.runtimeDir];
|
|
45
48
|
const environment = { ...options.environment };
|
|
49
|
+
const invoke = async (arguments_, commandTimeoutMs) => await run(command.executable, [command.entry, ...arguments_], environment, commandTimeoutMs);
|
|
46
50
|
try {
|
|
47
|
-
const probe = await
|
|
51
|
+
const probe = await invoke(["probe", ...common], Math.min(4_000, timeoutMs));
|
|
48
52
|
const existing = parseResult(probe.stdout);
|
|
49
53
|
if (probe.code === 0) {
|
|
50
54
|
if (existing?.generation === 1 && typeof existing.endpoint === "string") {
|
|
51
|
-
return { status: "ready", executable, endpoint: existing.endpoint };
|
|
55
|
+
return { status: "ready", executable: command, endpoint: existing.endpoint };
|
|
52
56
|
}
|
|
53
57
|
return {
|
|
54
58
|
status: "fallback",
|
|
@@ -61,11 +65,11 @@ export async function ensureTerminalHost(options) {
|
|
|
61
65
|
return { status: "fallback", reason: classifiedReason(existing?.reason, "host probe failed") };
|
|
62
66
|
const maximum = Math.max(1, Math.min(8, Math.trunc(options.maxConcurrentStarts ?? 2)));
|
|
63
67
|
const unresponsiveRestartSeconds = Math.max(30, Math.min(600, Math.trunc(options.unresponsiveRestartSeconds ?? 120)));
|
|
64
|
-
const start = await
|
|
68
|
+
const start = await invoke([
|
|
65
69
|
"server", "--detach", ...common,
|
|
66
70
|
"--max-concurrent-starts", String(maximum),
|
|
67
71
|
"--unresponsive-restart-seconds", String(unresponsiveRestartSeconds),
|
|
68
|
-
],
|
|
72
|
+
], timeoutMs);
|
|
69
73
|
const started = parseResult(start.stdout);
|
|
70
74
|
if (start.code !== 0)
|
|
71
75
|
return { status: "fallback", reason: classifiedReason(started?.reason, "launch not verified") };
|
|
@@ -73,11 +77,11 @@ export async function ensureTerminalHost(options) {
|
|
|
73
77
|
return { status: "fallback", reason: "launch not verified" };
|
|
74
78
|
const deadline = Date.now() + timeoutMs;
|
|
75
79
|
while (Date.now() < deadline) {
|
|
76
|
-
const result = await
|
|
80
|
+
const result = await invoke(["probe", ...common], Math.min(1_000, Math.max(1, deadline - Date.now())));
|
|
77
81
|
const ready = parseResult(result.stdout);
|
|
78
82
|
if (result.code === 0) {
|
|
79
83
|
if (ready?.generation === 1 && typeof ready.endpoint === "string") {
|
|
80
|
-
return { status: "ready", executable, endpoint: ready.endpoint };
|
|
84
|
+
return { status: "ready", executable: command, endpoint: ready.endpoint };
|
|
81
85
|
}
|
|
82
86
|
return {
|
|
83
87
|
status: "fallback",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { PRODUCT_IDENTITY } from "../../product-identity.js";
|
|
5
|
+
const filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const target = `${process.platform}-${process.arch}`;
|
|
7
|
+
const development = process.env[PRODUCT_IDENTITY.environment.developmentInstanceId]?.trim();
|
|
8
|
+
const override = development ? process.env[PRODUCT_IDENTITY.environment.terminalHostPath]?.trim() : undefined;
|
|
9
|
+
const addonPath = resolve(override || resolve(dirname(filename), "../../native", target, `${PRODUCT_IDENTITY.artifacts.terminalHostAddon}.node`));
|
|
10
|
+
const addon = createRequire(import.meta.url)(addonPath);
|
|
11
|
+
if (typeof addon.runRole !== "function")
|
|
12
|
+
throw new Error("terminal host addon exports are incompatible");
|
|
13
|
+
// Invariant: runRole blocks Node's main thread, so this entry must not register JavaScript signal
|
|
14
|
+
// listeners whose callbacks cannot run until the native role has already returned. Default process
|
|
15
|
+
// signal handling keeps attach clients and identity-verified resident roles externally stoppable.
|
|
16
|
+
process.env[PRODUCT_IDENTITY.environment.terminalHostPath] = addonPath;
|
|
17
|
+
process.exitCode = addon.runRole(process.argv.slice(2), { executable: process.execPath, entry: filename, artifact: addonPath });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { ensureTerminalHost, resolveTerminalHostPath } from "./ensure-terminal-host.js";
|
|
2
|
-
export type { EnsureTerminalHostOptions, EnsureTerminalHostResult, TerminalHostFallbackReason } from "./ensure-terminal-host.js";
|
|
1
|
+
export { ensureTerminalHost, resolveTerminalHostEntryPath, resolveTerminalHostPath } from "./ensure-terminal-host.js";
|
|
2
|
+
export type { EnsureTerminalHostOptions, EnsureTerminalHostResult, TerminalHostCommand, TerminalHostFallbackReason } from "./ensure-terminal-host.js";
|
|
3
3
|
export { readResidentTabLaunchSettings } from "./settings.js";
|
|
4
4
|
export type { ResidentTabLaunchSettings } from "./settings.js";
|
|
5
5
|
export { selectResidentHost } from "./routing.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { ensureTerminalHost, resolveTerminalHostPath } from "./ensure-terminal-host.js";
|
|
1
|
+
export { ensureTerminalHost, resolveTerminalHostEntryPath, resolveTerminalHostPath } from "./ensure-terminal-host.js";
|
|
2
2
|
export { readResidentTabLaunchSettings } from "./settings.js";
|
|
3
3
|
export { selectResidentHost } from "./routing.js";
|
|
4
4
|
export { readResidentReleaseHolds } from "./release-holds.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type TerminalHostCommand } from "./ensure-terminal-host.js";
|
|
1
2
|
export type MaintenanceAction = "list" | "stop" | "stop-all" | "host-status" | "host-stop";
|
|
2
3
|
export type MaintenanceHostState = "none" | "running" | "stopped" | "unresponsive" | "incompatible" | "refused" | "unverifiable" | "unavailable";
|
|
3
4
|
/** The versioned `a1 tabs` report: `a1-tabs-maintenance-v1`, as printed by the terminal host. */
|
|
@@ -26,16 +27,16 @@ export interface MaintenanceOptions {
|
|
|
26
27
|
readonly platform?: NodeJS.Platform;
|
|
27
28
|
readonly architecture?: string;
|
|
28
29
|
readonly timeoutMs?: number;
|
|
29
|
-
readonly verify?: (
|
|
30
|
-
readonly run?: (
|
|
30
|
+
readonly verify?: (addon: string, platform: NodeJS.Platform, architecture: string) => Promise<unknown>;
|
|
31
|
+
readonly run?: (command: TerminalHostCommand, arguments_: readonly string[], timeoutMs: number) => Promise<{
|
|
31
32
|
readonly code: number;
|
|
32
33
|
readonly stdout: string;
|
|
33
34
|
}>;
|
|
34
35
|
}
|
|
35
36
|
/**
|
|
36
37
|
* Runs one maintenance form through a verified terminal host. When the running host speaks
|
|
37
|
-
* another protocol generation, it retries once with the
|
|
38
|
-
* recorded, after verifying that release's manifest; it never
|
|
38
|
+
* another protocol generation, it retries once with the addon from the release that host
|
|
39
|
+
* recorded, after verifying that release's manifest; it never loads an unverified addon.
|
|
39
40
|
*/
|
|
40
41
|
export declare function runTerminalHostMaintenance(options: MaintenanceOptions): Promise<MaintenanceReport>;
|
|
41
42
|
/** The largest maintenance report accepted from the terminal host. */
|