@timurproko/a1 0.2.7-dev.788 → 0.2.7-dev.794
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/session-shell-root.d.ts +2 -1
- package/dist/app/session-shell/session-shell-root.js +41 -15
- package/dist/app/session-shell/session-shell.d.ts +1 -0
- package/dist/app/session-shell/session-shell.js +15 -4
- package/dist/app/session-shell/session-viewport-controller.d.ts +2 -2
- package/dist/app/session-shell/session-viewport-controller.js +24 -12
- package/dist/cli/tabs.js +1 -1
- package/dist/composition/owned-ui.d.ts +2 -0
- package/dist/composition/owned-ui.js +42 -4
- package/dist/contracts/owned-ui/index.d.ts +1 -1
- package/dist/contracts/owned-ui/model.d.ts +3 -0
- package/dist/features/owned-ui/settings-app.js +9 -6
- package/dist/features/resident-tab/index.d.ts +3 -1
- package/dist/features/resident-tab/index.js +5 -1
- package/dist/features/resident-tab/shortcut-bindings-file.d.ts +8 -0
- package/dist/features/resident-tab/shortcut-bindings-file.js +40 -0
- package/dist/features/resident-tab/shortcut-declarations.d.ts +13 -0
- package/dist/features/resident-tab/{shortcuts.js → shortcut-declarations.js} +6 -17
- package/dist/features/resident-tab/shortcut-projection.d.ts +29 -0
- package/dist/features/resident-tab/shortcut-projection.js +64 -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 +54 -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 +7 -0
- package/dist/foundation/resident-tabs/bridge-client.js +30 -1
- 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/prompt-journal.d.ts +0 -2
- package/dist/foundation/resident-tabs/prompt-journal.js +0 -1
- 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/owned-editor-ux.d.ts +2 -0
- package/dist/integrations/pi/components/owned-editor-ux.js +13 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +3 -1
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +1 -0
- package/dist/integrations/pi/components/shell-presenters-info.js +10 -30
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +4 -0
- package/dist/native/darwin-arm64/manifest.json +10 -7
- 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 +16 -14
- package/dist/native/darwin-arm64/terminal-host.node +0 -0
- package/dist/native/darwin-x64/manifest.json +10 -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 +16 -14
- package/dist/native/darwin-x64/terminal-host.node +0 -0
- package/dist/native/linux-x64/manifest.json +10 -7
- 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 +16 -14
- package/dist/native/linux-x64/terminal-host.node +0 -0
- package/dist/native/win32-x64/manifest.json +10 -7
- 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 +16 -14
- package/dist/native/win32-x64/terminal-host.node +0 -0
- package/dist/product-identity.d.ts +1 -1
- package/dist/product-identity.js +1 -1
- package/dist/product-identity.json +3 -2
- package/dist/runtime-payload-inventory.json +1 -0
- package/dist/ui/components/index.d.ts +2 -2
- package/dist/ui/components/index.js +1 -1
- package/dist/ui/components/selection-copy.d.ts +2 -0
- package/dist/ui/components/selection-copy.js +13 -0
- package/dist/ui/components/submitted-prompt.d.ts +7 -4
- package/dist/ui/components/submitted-prompt.js +18 -10
- package/dist/ui/components/transcript-viewport.d.ts +10 -6
- package/dist/ui/components/transcript-viewport.js +28 -13
- package/dist/ui/settings/declarations.d.ts +13 -13
- package/dist/ui/settings/declarations.js +12 -11
- package/dist/ui/settings/migrations.js +7 -0
- package/docs/architecture/boundaries.md +10 -9
- package/docs/architecture/evidence/resident-tabs/macos-x64.md +1 -1
- package/docs/architecture/process-guardian-provenance.md +17 -11
- package/docs/architecture/project-structure.md +8 -0
- package/docs/architecture/resident-tabs.md +54 -36
- package/docs/architecture/terminal-host-provenance.md +6 -4
- package/docs/architecture/toolchain.md +6 -6
- package/docs/openspec-archive-automation.md +3 -3
- package/docs/validation.md +7 -3
- package/package.json +1 -1
- package/dist/features/resident-tab/shortcuts.d.ts +0 -10
- 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/linux-x64/terminal-host +0 -0
- package/dist/native/win32-x64/a1_session_lock.node +0 -0
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/native/win32-x64/terminal-host.exe +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
const cache = new Map();
|
|
5
|
+
/** Loads one verified Node-API guardian instance per exact addon path. */
|
|
6
|
+
export function loadProcessGuardianAddon(path) {
|
|
7
|
+
if (!path || path.includes("\0"))
|
|
8
|
+
throw new TypeError("process guardian addon path is invalid");
|
|
9
|
+
const exactPath = resolve(path);
|
|
10
|
+
const cached = cache.get(exactPath);
|
|
11
|
+
if (cached)
|
|
12
|
+
return cached;
|
|
13
|
+
let value;
|
|
14
|
+
try {
|
|
15
|
+
value = require(exactPath);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const code = errorCode(error) === "MODULE_NOT_FOUND" || errorCode(error) === "ENOENT"
|
|
19
|
+
? "CONTAINMENT_ARTIFACT_MISSING"
|
|
20
|
+
: "CONTAINMENT_ARTIFACT_INCOMPATIBLE";
|
|
21
|
+
throw diagnosticError(`process guardian addon could not be loaded: ${errorMessage(error)}`, code);
|
|
22
|
+
}
|
|
23
|
+
if (!isProcessGuardianAddon(value)) {
|
|
24
|
+
throw diagnosticError("process guardian addon exports are incompatible", "CONTAINMENT_ARTIFACT_INCOMPATIBLE");
|
|
25
|
+
}
|
|
26
|
+
let report;
|
|
27
|
+
try {
|
|
28
|
+
report = value.version();
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
throw diagnosticError(`process guardian addon identity failed: ${errorMessage(error)}`, "CONTAINMENT_ARTIFACT_INCOMPATIBLE");
|
|
32
|
+
}
|
|
33
|
+
if (report.protocolVersion !== 2 || typeof report.crateVersion !== "string" || report.crateVersion.length === 0 || typeof report.target !== "string") {
|
|
34
|
+
throw diagnosticError("process guardian addon protocol is incompatible", "CONTAINMENT_ARTIFACT_INCOMPATIBLE");
|
|
35
|
+
}
|
|
36
|
+
cache.set(exactPath, value);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function isProcessGuardianAddon(value) {
|
|
40
|
+
if (typeof value !== "object" || value === null)
|
|
41
|
+
return false;
|
|
42
|
+
const candidate = value;
|
|
43
|
+
return [
|
|
44
|
+
"version", "inspectProcessStart", "captureTerminalModes", "restoreTerminalModes",
|
|
45
|
+
"deliverExitNotice", "spawnContained", "spawnWatcher", "waitForProcessExit",
|
|
46
|
+
"stopProcessGroup", "restoreForegroundGroup", "ignoreConsoleControl",
|
|
47
|
+
].every(name => typeof candidate[name] === "function");
|
|
48
|
+
}
|
|
49
|
+
function diagnosticError(message, code) {
|
|
50
|
+
return Object.assign(new Error(message), { code });
|
|
51
|
+
}
|
|
52
|
+
function errorCode(error) {
|
|
53
|
+
return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : null;
|
|
54
|
+
}
|
|
55
|
+
function errorMessage(error) {
|
|
56
|
+
return error instanceof Error ? error.message : String(error);
|
|
57
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function verifyProcessGuardianArtifact(
|
|
1
|
+
export declare function verifyProcessGuardianArtifact(addonPath: string, platform?: NodeJS.Platform, architecture?: string): Promise<void>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { readFile, stat } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, resolve } from "node:path";
|
|
4
|
-
export async function verifyProcessGuardianArtifact(
|
|
5
|
-
const manifestPath = resolve(dirname(
|
|
4
|
+
export async function verifyProcessGuardianArtifact(addonPath, platform = process.platform, architecture = process.arch) {
|
|
5
|
+
const manifestPath = resolve(dirname(addonPath), "manifest.json");
|
|
6
6
|
let manifest;
|
|
7
7
|
try {
|
|
8
8
|
manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
@@ -10,15 +10,16 @@ export async function verifyProcessGuardianArtifact(helperPath, platform = proce
|
|
|
10
10
|
catch (error) {
|
|
11
11
|
throw artifactError(`process guardian manifest is missing or malformed: ${errorMessage(error)}`, "CONTAINMENT_ARTIFACT_MISSING");
|
|
12
12
|
}
|
|
13
|
-
if (manifest.schema !== "a1-process-guardian-artifact-
|
|
13
|
+
if (manifest.schema !== "a1-process-guardian-artifact-v2" || manifest.kind !== "node-addon"
|
|
14
|
+
|| manifest.nodeApi !== 9 || manifest.protocolVersion !== 2
|
|
14
15
|
|| manifest.platform !== platform || manifest.architecture !== architecture
|
|
15
|
-
|| manifest.artifact?.filename !== basename(
|
|
16
|
+
|| manifest.artifact?.filename !== basename(addonPath)) {
|
|
16
17
|
throw artifactError("process guardian manifest is incompatible with this platform or protocol", "CONTAINMENT_ARTIFACT_INCOMPATIBLE");
|
|
17
18
|
}
|
|
18
19
|
if (manifest.capability !== "supported") {
|
|
19
20
|
throw artifactError(`process containment is not certified for ${platform}-${architecture}`, "CONTAINMENT_UNSUPPORTED");
|
|
20
21
|
}
|
|
21
|
-
const [bytes, metadata] = await Promise.all([readFile(
|
|
22
|
+
const [bytes, metadata] = await Promise.all([readFile(addonPath), stat(addonPath)]).catch(error => {
|
|
22
23
|
throw artifactError(`process guardian artifact is unavailable: ${errorMessage(error)}`, "CONTAINMENT_ARTIFACT_MISSING");
|
|
23
24
|
});
|
|
24
25
|
const digest = createHash("sha256").update(bytes).digest("hex");
|
|
@@ -7,6 +7,8 @@ export interface ContainedSpawnOptions {
|
|
|
7
7
|
export interface ContainedProcessHandle {
|
|
8
8
|
readonly identity: NativeProcessIdentity;
|
|
9
9
|
readonly outcome: Promise<LaunchInstanceOutcome>;
|
|
10
|
+
readonly processGroup?: number;
|
|
11
|
+
readonly foregroundGroup?: number;
|
|
10
12
|
}
|
|
11
13
|
export interface NativeProcessInspector {
|
|
12
14
|
observe(pid: number): Promise<NativeProcessIdentity | null>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
export { loadProcessGuardianAddon } from "./addon.js";
|
|
2
|
+
export type { ProcessGuardianAddon, ProcessGuardianRuntime, ProcessGuardianVersion } from "./addon.js";
|
|
3
|
+
export { AddonContainment, resolveProcessGuardianPath } from "./addon-containment.js";
|
|
4
|
+
export { AddonProcessInspector } from "./addon-process-inspector.js";
|
|
1
5
|
export { verifyProcessGuardianArtifact } from "./artifact.js";
|
|
2
6
|
export { closeVerifiedContainment } from "./contracts.js";
|
|
3
7
|
export type { ContainedProcessHandle, ContainedSpawnOptions, ContainmentCloseResult, NativeProcessInspector, ProcessContainment, ProcessContainmentFactory, } from "./contracts.js";
|
|
4
|
-
export { DarwinNativeProcessInspector } from "./darwin-process-inspector.js";
|
|
5
8
|
export { LinuxNativeProcessInspector } from "./linux-process-inspector.js";
|
|
6
|
-
export { NativeGuardianContainment, resolveProcessGuardianPath } from "./native-guardian-containment.js";
|
|
7
9
|
export { createNativeProcessInspector } from "./native-process-inspector.js";
|
|
8
|
-
export { WindowsNativeProcessInspector } from "./windows-process-inspector.js";
|
|
9
|
-
export type { InspectorCommandResult, InspectorCommandRunner } from "./windows-process-inspector.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
export { loadProcessGuardianAddon } from "./addon.js";
|
|
2
|
+
export { AddonContainment, resolveProcessGuardianPath } from "./addon-containment.js";
|
|
3
|
+
export { AddonProcessInspector } from "./addon-process-inspector.js";
|
|
1
4
|
export { verifyProcessGuardianArtifact } from "./artifact.js";
|
|
2
5
|
export { closeVerifiedContainment } from "./contracts.js";
|
|
3
|
-
export { DarwinNativeProcessInspector } from "./darwin-process-inspector.js";
|
|
4
6
|
export { LinuxNativeProcessInspector } from "./linux-process-inspector.js";
|
|
5
|
-
export { NativeGuardianContainment, resolveProcessGuardianPath } from "./native-guardian-containment.js";
|
|
6
7
|
export { createNativeProcessInspector } from "./native-process-inspector.js";
|
|
7
|
-
export { WindowsNativeProcessInspector } from "./windows-process-inspector.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ProcessGuardianAddon } from "./addon.js";
|
|
1
2
|
import type { NativeProcessInspector } from "./contracts.js";
|
|
2
3
|
/** Resolve only the platform's exact-process inspector used by cooperative session ownership. */
|
|
3
|
-
export declare function createNativeProcessInspector(environment?: NodeJS.ProcessEnv, platform?: NodeJS.Platform, releaseRoot?: string): Promise<NativeProcessInspector>;
|
|
4
|
+
export declare function createNativeProcessInspector(environment?: NodeJS.ProcessEnv, platform?: NodeJS.Platform, releaseRoot?: string, loadAddon?: (path: string) => ProcessGuardianAddon): Promise<NativeProcessInspector>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PRODUCT_IDENTITY } from "../../product-identity.js";
|
|
2
2
|
/** Resolve only the platform's exact-process inspector used by cooperative session ownership. */
|
|
3
|
-
export async function createNativeProcessInspector(environment = process.env, platform = process.platform, releaseRoot) {
|
|
3
|
+
export async function createNativeProcessInspector(environment = process.env, platform = process.platform, releaseRoot, loadAddon) {
|
|
4
4
|
if (platform === "linux") {
|
|
5
5
|
const { LinuxNativeProcessInspector } = await import("./linux-process-inspector.js");
|
|
6
6
|
return new LinuxNativeProcessInspector();
|
|
@@ -8,14 +8,14 @@ export async function createNativeProcessInspector(environment = process.env, pl
|
|
|
8
8
|
if (platform !== "win32" && platform !== "darwin")
|
|
9
9
|
throw new Error(`native process identity is unsupported on ${platform}`);
|
|
10
10
|
if (releaseRoot === undefined && !environment[PRODUCT_IDENTITY.environment.processGuardianPath]) {
|
|
11
|
-
throw new Error("native process identity
|
|
11
|
+
throw new Error("native process identity addon is unavailable");
|
|
12
12
|
}
|
|
13
|
-
const { resolveProcessGuardianPath } = await
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
return new
|
|
13
|
+
const [{ resolveProcessGuardianPath }, { loadProcessGuardianAddon }, { AddonProcessInspector }] = await Promise.all([
|
|
14
|
+
import("./addon-containment.js"),
|
|
15
|
+
import("./addon.js"),
|
|
16
|
+
import("./addon-process-inspector.js"),
|
|
17
|
+
]);
|
|
18
|
+
const addonPath = resolveProcessGuardianPath(releaseRoot ?? process.cwd(), environment, platform);
|
|
19
|
+
const addon = (loadAddon ?? loadProcessGuardianAddon)(addonPath);
|
|
20
|
+
return new AddonProcessInspector(addon, platform === "win32" ? "windows-filetime:" : "darwin-proc-start:");
|
|
21
21
|
}
|
|
@@ -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,11 +61,12 @@ 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
|
+
"--settings-file", resolve(agentProfileDir, "settings.json"),
|
|
69
70
|
"--data-dir", paths.dataDir, "--runtime-dir", paths.runtimeDir,
|
|
70
71
|
"--release-root", release.releaseRoot, "--node-executable", process.execPath,
|
|
71
72
|
]),
|
|
@@ -125,7 +126,23 @@ async function runBootstrapAttempt(options, retriesRemaining) {
|
|
|
125
126
|
const installedVersion = await readInstalledVersion(options.packageRoot);
|
|
126
127
|
const activeId = state.references.active;
|
|
127
128
|
const active = activeId === null ? undefined : state.releases[activeId];
|
|
128
|
-
|
|
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
|
+
}
|
|
129
146
|
if (activeIsLaunchable && active?.approval === "approved" && active.packageVersion === installedVersion) {
|
|
130
147
|
const endpointMatches = endpoint?.releaseId === active.releaseId
|
|
131
148
|
&& endpoint.releaseRoot === active.releaseRoot
|
|
@@ -139,18 +156,27 @@ async function runBootstrapAttempt(options, retriesRemaining) {
|
|
|
139
156
|
await removeEndpointArtifacts(endpointPaths.endpointMetadataPath, endpointPaths.endpoint);
|
|
140
157
|
await markStartupPhase(environment, "durable-validation-start");
|
|
141
158
|
const retained = await readRestartCertifiedRelease(active, paths.dataDir).catch(async () => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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;
|
|
145
166
|
}
|
|
167
|
+
assertApprovedReleaseMatches(verified, active);
|
|
146
168
|
await certifyMaterializedRelease(verified, paths.dataDir);
|
|
147
169
|
return verified;
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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;
|
|
154
180
|
}
|
|
155
181
|
}
|
|
156
182
|
// Concurrency: an installation being replaced is not a reason to refuse a launch. While
|
|
@@ -162,7 +188,7 @@ async function runBootstrapAttempt(options, retriesRemaining) {
|
|
|
162
188
|
candidate = await materializeRelease(options.packageRoot, paths.dataDir);
|
|
163
189
|
}
|
|
164
190
|
catch (error) {
|
|
165
|
-
const fallback = await launchRetainedActive(state, paths, environment, launchSelected);
|
|
191
|
+
const fallback = await launchRetainedActive(state, paths, environment, launchSelected, excludedRetainedReleaseId);
|
|
166
192
|
if (fallback === null)
|
|
167
193
|
throw error;
|
|
168
194
|
return fallback;
|
|
@@ -289,10 +315,10 @@ async function runBootstrapAttempt(options, retriesRemaining) {
|
|
|
289
315
|
* certifying anything new, and reports no selection when there is nothing safe
|
|
290
316
|
* to fall back to.
|
|
291
317
|
*/
|
|
292
|
-
async function launchRetainedActive(state, paths, environment, launch) {
|
|
318
|
+
async function launchRetainedActive(state, paths, environment, launch, excludedReleaseId) {
|
|
293
319
|
const activeId = state.references.active;
|
|
294
320
|
const active = activeId === null ? undefined : state.releases[activeId];
|
|
295
|
-
if (!active || active.approval !== "approved")
|
|
321
|
+
if (!active || active.approval !== "approved" || active.releaseId === excludedReleaseId)
|
|
296
322
|
return null;
|
|
297
323
|
try {
|
|
298
324
|
const retained = await readCertifiedReleaseManifest(active, resolve(paths.dataDir, "releases"));
|
|
@@ -303,6 +329,18 @@ async function launchRetainedActive(state, paths, environment, launch) {
|
|
|
303
329
|
return null;
|
|
304
330
|
}
|
|
305
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
|
+
}
|
|
306
344
|
async function readInstalledVersion(packageRoot) {
|
|
307
345
|
const manifest = JSON.parse(await readFile(resolve(packageRoot, "package.json"), "utf8"));
|
|
308
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;
|
|
@@ -15,6 +15,9 @@ export type TabBridgeCommand = {
|
|
|
15
15
|
readonly type: "rename";
|
|
16
16
|
readonly name: string;
|
|
17
17
|
readonly nameSource: "default" | "user" | "auto";
|
|
18
|
+
} | {
|
|
19
|
+
readonly type: "route";
|
|
20
|
+
readonly route: "settings";
|
|
18
21
|
} | {
|
|
19
22
|
readonly type: "stop";
|
|
20
23
|
readonly deadlineMs: number;
|
|
@@ -39,6 +42,7 @@ export interface TabBridgeOptions {
|
|
|
39
42
|
export declare class TabBridgeClient {
|
|
40
43
|
#private;
|
|
41
44
|
constructor(options: TabBridgeOptions);
|
|
45
|
+
get tabId(): string;
|
|
42
46
|
get available(): boolean;
|
|
43
47
|
captureAttachment(): TabAttachment | null;
|
|
44
48
|
onCommand(listener: (command: TabBridgeCommand) => void): () => void;
|
|
@@ -48,8 +52,11 @@ export declare class TabBridgeClient {
|
|
|
48
52
|
accent(accent: string): void;
|
|
49
53
|
session(file: string | null, name: string | null, nameSource?: "default" | "user" | "auto"): void;
|
|
50
54
|
prompt(interrupted: boolean): void;
|
|
55
|
+
/** Publishes the effective tab shortcut assignments so the host dispatches and labels the same keys. */
|
|
56
|
+
shortcuts(assignments: Readonly<Record<string, string | null>>): void;
|
|
51
57
|
requestDetach(origin?: TabAttachment | null, timeoutMs?: number): Promise<boolean>;
|
|
52
58
|
requestRestart(origin?: TabAttachment | null, timeoutMs?: number): Promise<boolean>;
|
|
59
|
+
requestClose(origin?: TabAttachment | null, timeoutMs?: number): Promise<boolean>;
|
|
53
60
|
consumeControllerBoundaryInput(data: string): string;
|
|
54
61
|
close(): void;
|
|
55
62
|
}
|