@timurproko/a1 0.2.7-dev.767 → 0.2.7-dev.772
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/bin/cli.js +4 -0
- package/bin/tab-launch-context.d.ts +3 -0
- package/bin/tab-launch-context.js +52 -3
- package/bin/ui.js +6 -3
- package/dist/app/session-shell/session-shell-root.d.ts +0 -1
- package/dist/app/session-shell/session-shell-root.js +1 -1
- package/dist/app/session-shell/session-shell.js +3 -8
- package/dist/app/session-shell/shell-host.d.ts +2 -0
- package/dist/app/session-shell/shell-host.js +20 -1
- package/dist/cli/dispatch.d.ts +11 -1
- package/dist/cli/dispatch.js +79 -5
- package/dist/cli/tabs-request.d.ts +7 -0
- package/dist/cli/tabs-request.js +1 -0
- package/dist/cli/tabs.d.ts +14 -0
- package/dist/cli/tabs.js +140 -0
- package/dist/composition/owned-ui.d.ts +8 -2
- package/dist/composition/owned-ui.js +109 -12
- package/dist/features/launch/index.d.ts +1 -1
- package/dist/features/launch/intent.d.ts +3 -1
- package/dist/features/launch/intent.js +7 -4
- package/dist/features/resident-tab/auto-name.d.ts +11 -0
- package/dist/features/resident-tab/auto-name.js +42 -0
- package/dist/features/resident-tab/index.d.ts +3 -0
- package/dist/features/resident-tab/index.js +3 -0
- package/dist/features/resident-tab/shortcuts.d.ts +10 -0
- package/dist/features/resident-tab/shortcuts.js +35 -0
- package/dist/features/resident-tab/status-deriver.d.ts +9 -0
- package/dist/features/resident-tab/status-deriver.js +15 -0
- package/dist/foundation/launch-context/index.d.ts +4 -0
- package/dist/foundation/launch-context/index.js +10 -0
- package/dist/foundation/release/bootstrap.d.ts +4 -2
- package/dist/foundation/release/bootstrap.js +11 -8
- package/dist/foundation/release/cohort-state.d.ts +1 -1
- package/dist/foundation/release/cohort-state.js +1 -1
- package/dist/foundation/release/release-gc.d.ts +3 -0
- package/dist/foundation/release/release-gc.js +44 -8
- package/dist/foundation/resident-tabs/bounded-host-command.d.ts +16 -0
- package/dist/foundation/resident-tabs/bounded-host-command.js +35 -0
- package/dist/foundation/resident-tabs/bridge-client.d.ts +18 -3
- package/dist/foundation/resident-tabs/bridge-client.js +87 -7
- package/dist/foundation/resident-tabs/doctor.d.ts +29 -0
- package/dist/foundation/resident-tabs/doctor.js +166 -0
- package/dist/foundation/resident-tabs/ensure-terminal-host.d.ts +2 -1
- package/dist/foundation/resident-tabs/ensure-terminal-host.js +33 -7
- package/dist/foundation/resident-tabs/index.d.ts +7 -1
- package/dist/foundation/resident-tabs/index.js +4 -1
- package/dist/foundation/resident-tabs/maintenance.d.ts +42 -0
- package/dist/foundation/resident-tabs/maintenance.js +78 -0
- package/dist/foundation/resident-tabs/release-holds.d.ts +27 -0
- package/dist/foundation/resident-tabs/release-holds.js +77 -0
- package/dist/foundation/resident-tabs/routing.d.ts +4 -3
- package/dist/foundation/resident-tabs/routing.js +6 -4
- package/dist/foundation/resident-tabs/settings.d.ts +7 -2
- package/dist/foundation/resident-tabs/settings.js +17 -16
- package/dist/foundation/resident-tabs/terminal-host-artifact.d.ts +18 -0
- package/dist/foundation/resident-tabs/terminal-host-artifact.js +53 -0
- package/dist/foundation/resident-tabs/zip-archive.d.ts +9 -0
- package/dist/foundation/resident-tabs/zip-archive.js +56 -0
- package/dist/foundation/session-writer-lock/index.d.ts +5 -0
- package/dist/foundation/session-writer-lock/index.js +13 -9
- package/dist/integrations/pi/components/shell-hotkey-sections.js +1 -1
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +4 -2
- package/dist/integrations/pi/components/shell-presenters-info.js +38 -4
- package/dist/integrations/pi/engine/adapter.d.ts +1 -0
- package/dist/integrations/pi/engine/adapter.js +3 -0
- package/dist/integrations/pi/engine/prompt-suggestions.d.ts +1 -0
- package/dist/integrations/pi/engine/prompt-suggestions.js +40 -0
- package/dist/native/darwin-arm64/a1_session_lock.node +0 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/darwin-arm64/terminal-host +0 -0
- package/dist/native/darwin-arm64/terminal-host-THIRD-PARTY-NOTICES.txt +10500 -0
- package/dist/native/darwin-arm64/terminal-host.manifest.json +44 -0
- package/dist/native/darwin-x64/a1_session_lock.node +0 -0
- package/dist/native/darwin-x64/manifest.json +20 -0
- package/dist/native/darwin-x64/process-guardian +0 -0
- package/dist/native/darwin-x64/terminal-host +0 -0
- package/dist/native/darwin-x64/terminal-host-THIRD-PARTY-NOTICES.txt +10737 -0
- package/dist/native/darwin-x64/terminal-host.manifest.json +44 -0
- package/dist/native/linux-x64/a1_session_lock.node +0 -0
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/linux-x64/terminal-host +0 -0
- package/dist/native/linux-x64/terminal-host-THIRD-PARTY-NOTICES.txt +11226 -0
- package/dist/native/linux-x64/terminal-host.manifest.json +44 -0
- package/dist/native/win32-x64/a1_session_lock.node +0 -0
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/native/win32-x64/terminal-host-THIRD-PARTY-NOTICES.txt +10757 -0
- package/dist/native/win32-x64/terminal-host.exe +0 -0
- package/dist/native/win32-x64/terminal-host.manifest.json +44 -0
- package/dist/ui/settings/declarations.d.ts +19 -43
- package/dist/ui/settings/declarations.js +19 -35
- package/dist/ui/settings/index.d.ts +1 -1
- package/dist/ui/settings/index.js +1 -1
- package/dist/ui/settings/manager.d.ts +1 -0
- package/dist/ui/settings/manager.js +9 -1
- package/dist/ui/settings/migrations.js +12 -0
- package/dist/ui/settings/resolution.d.ts +0 -2
- package/dist/ui/settings/resolution.js +1 -13
- package/dist/ui/settings/sections.js +0 -4
- package/docs/architecture/evidence/resident-tabs/linux-x64.md +37 -0
- package/docs/architecture/evidence/resident-tabs/macos-arm64.md +36 -0
- package/docs/architecture/evidence/resident-tabs/macos-x64.md +35 -0
- package/docs/architecture/evidence/resident-tabs/windows-x64.md +36 -0
- package/docs/architecture/internal-naming.md +2 -0
- package/docs/architecture/process-guardian-provenance.md +2 -0
- package/docs/architecture/resident-tabs-requirements.md +1 -563
- package/docs/architecture/resident-tabs.md +166 -94
- package/docs/architecture/terminal-host-provenance.md +13 -3
- package/docs/architecture/toolchain.md +1 -1
- package/docs/ci-release-runbook.md +2 -2
- package/docs/features/launch-profiles.md +11 -8
- package/docs/manual-resident-tabs-acceptance.md +51 -0
- package/docs/validation.md +9 -1
- package/package.json +5 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir, release as osRelease } from "node:os";
|
|
4
|
+
import { basename, dirname, resolve, sep } from "node:path";
|
|
5
|
+
import { resolveTerminalHostPath } from "./ensure-terminal-host.js";
|
|
6
|
+
import { TERMINAL_HOST_MANIFEST_FILENAME, verifyTerminalHostArtifact } from "./terminal-host-artifact.js";
|
|
7
|
+
import { encodeZip } from "./zip-archive.js";
|
|
8
|
+
export const DOCTOR_MAX_BYTES = 25 * 1024 * 1024;
|
|
9
|
+
const LOG_NAME = /^terminal-host-[a-z0-9-]+\.log(?:\.(\d{1,2}))?$/u;
|
|
10
|
+
const CRASH_NAME = /^crash-[a-z0-9-]+\.json$/u;
|
|
11
|
+
/**
|
|
12
|
+
* Writes an owner-only diagnostic bundle by allowlist: structured logs, crash records, host
|
|
13
|
+
* status, a redacted registry, the owner marker without secrets, artifact verification, platform
|
|
14
|
+
* facts, and the Tabs settings. Secrets, tokens, environment values, prompt journals, drafts,
|
|
15
|
+
* last screens, the private child-stderr sink, and terminal content are never read.
|
|
16
|
+
*/
|
|
17
|
+
export async function writeDoctorBundle(options) {
|
|
18
|
+
const home = options.home ?? homedir();
|
|
19
|
+
const redact = (value) => homeRelative(value, home);
|
|
20
|
+
const now = options.now ?? new Date();
|
|
21
|
+
const maxBytes = options.maxBytes ?? DOCTOR_MAX_BYTES;
|
|
22
|
+
const diagnostics = resolve(options.stateDir, "diagnostics");
|
|
23
|
+
const names = await readdir(diagnostics).catch(() => []);
|
|
24
|
+
const fixed = [
|
|
25
|
+
json("host-status.json", redactHostStatus(options.hostStatus, redact)),
|
|
26
|
+
json("registry.json", await redactedRegistry(resolve(options.stateDir, "registry.json"), redact)),
|
|
27
|
+
json("server-marker.json", await redactedMarker(resolve(options.stateDir, "server.json"), redact)),
|
|
28
|
+
json("artifacts.json", await artifactFacts(options.releaseRoots, options.platform ?? process.platform, options.architecture ?? process.arch, redact)),
|
|
29
|
+
json("platform.json", {
|
|
30
|
+
platform: options.platform ?? process.platform,
|
|
31
|
+
architecture: options.architecture ?? process.arch,
|
|
32
|
+
osRelease: osRelease(),
|
|
33
|
+
node: process.version,
|
|
34
|
+
detachmentMode: options.hostStatus.result?.detachmentMode ?? null,
|
|
35
|
+
}),
|
|
36
|
+
json("settings.json", { tabsMax: options.settings.tabsMax, tabsAutoName: options.settings.tabsAutoName }),
|
|
37
|
+
];
|
|
38
|
+
const crashes = [];
|
|
39
|
+
for (const name of names.filter(name => CRASH_NAME.test(name)).sort()) {
|
|
40
|
+
crashes.push({ name: `crashes/${name}`, data: await readFile(resolve(diagnostics, name)) });
|
|
41
|
+
}
|
|
42
|
+
// Rationale: when the bundle would exceed its cap, the oldest log generations go first.
|
|
43
|
+
const logs = names
|
|
44
|
+
.filter(name => LOG_NAME.test(name))
|
|
45
|
+
.map(name => ({ name, generation: Number(LOG_NAME.exec(name)?.[1] ?? 0) }))
|
|
46
|
+
.sort((left, right) => left.generation - right.generation || left.name.localeCompare(right.name));
|
|
47
|
+
const dropped = [];
|
|
48
|
+
const included = [...fixed, ...crashes];
|
|
49
|
+
let budget = maxBytes - 64 * 1024 - included.reduce((total, entry) => total + entry.data.length, 0);
|
|
50
|
+
for (const log of logs) {
|
|
51
|
+
const size = (await stat(resolve(diagnostics, log.name))).size;
|
|
52
|
+
if (size > budget) {
|
|
53
|
+
dropped.push(log.name);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
budget -= size;
|
|
57
|
+
included.push({ name: `logs/${log.name}`, data: await readFile(resolve(diagnostics, log.name)) });
|
|
58
|
+
}
|
|
59
|
+
const manifest = [
|
|
60
|
+
"a1 tabs doctor bundle",
|
|
61
|
+
`created: ${now.toISOString()}`,
|
|
62
|
+
...included.map(entry => `included: ${entry.name}`),
|
|
63
|
+
...dropped.map(name => `dropped to stay within ${maxBytes} bytes: logs/${name}`),
|
|
64
|
+
"",
|
|
65
|
+
].join("\n");
|
|
66
|
+
const archive = encodeZip([{ name: "manifest.txt", data: Buffer.from(manifest, "utf8") }, ...included], now);
|
|
67
|
+
const directory = resolve(options.stateDir, "doctor");
|
|
68
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
69
|
+
const path = resolve(directory, `a1-tabs-doctor-${now.toISOString().replace(/[:.]/gu, "-")}.zip`);
|
|
70
|
+
await writeFile(path, archive, { mode: 0o600, flag: "wx" });
|
|
71
|
+
return { path, bytes: archive.length, dropped };
|
|
72
|
+
}
|
|
73
|
+
function json(name, value) {
|
|
74
|
+
return { name, data: Buffer.from(`${JSON.stringify(value, null, 2)}\n`, "utf8") };
|
|
75
|
+
}
|
|
76
|
+
function stableHash(value) {
|
|
77
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
78
|
+
}
|
|
79
|
+
function homeRelative(value, home) {
|
|
80
|
+
const normalized = resolve(value);
|
|
81
|
+
const root = resolve(home);
|
|
82
|
+
const comparable = (path) => process.platform === "win32" ? path.toLowerCase() : path;
|
|
83
|
+
if (comparable(normalized) === comparable(root))
|
|
84
|
+
return "~";
|
|
85
|
+
return comparable(normalized).startsWith(comparable(`${root}${sep}`)) ? `~${normalized.slice(root.length).replaceAll("\\", "/")}` : normalized;
|
|
86
|
+
}
|
|
87
|
+
function redactHostStatus(report, redact) {
|
|
88
|
+
const result = report.result ? { ...report.result } : undefined;
|
|
89
|
+
if (result && Array.isArray(result.logs))
|
|
90
|
+
result.logs = result.logs.map(path => typeof path === "string" ? basename(path) : null);
|
|
91
|
+
if (result && typeof result.release === "string")
|
|
92
|
+
result.release = redact(result.release);
|
|
93
|
+
return {
|
|
94
|
+
host: report.host,
|
|
95
|
+
pid: report.pid ?? null,
|
|
96
|
+
exitCode: report.exitCode,
|
|
97
|
+
result: result ?? null,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async function redactedRegistry(path, redact) {
|
|
101
|
+
let document;
|
|
102
|
+
try {
|
|
103
|
+
document = JSON.parse(await readFile(path, "utf8"));
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
return { readable: false, reason: error instanceof Error && "code" in error ? String(error.code) : "unparseable" };
|
|
107
|
+
}
|
|
108
|
+
const tabs = Array.isArray(document.tabs) ? document.tabs : [];
|
|
109
|
+
const text = (value) => typeof value === "string" ? value : null;
|
|
110
|
+
return {
|
|
111
|
+
readable: true,
|
|
112
|
+
registryRevision: document.registryRevision ?? null,
|
|
113
|
+
epoch: document.epoch ?? null,
|
|
114
|
+
tabs: tabs.map(tab => {
|
|
115
|
+
const sessionFile = text(tab.sessionFile);
|
|
116
|
+
return {
|
|
117
|
+
tabId: text(tab.tabId),
|
|
118
|
+
// Security: names can be generated from prompts, so only a stable hash is kept.
|
|
119
|
+
nameHash: text(tab.displayName) === null ? null : stableHash(text(tab.displayName)),
|
|
120
|
+
nameSource: text(tab.nameSource),
|
|
121
|
+
order: tab.order ?? null,
|
|
122
|
+
cwd: text(tab.cwd) === null ? null : redact(text(tab.cwd)),
|
|
123
|
+
sessionDirectory: sessionFile === null ? null : redact(dirname(sessionFile)),
|
|
124
|
+
sessionFileHash: sessionFile === null ? null : stableHash(basename(sessionFile)),
|
|
125
|
+
desired: text(tab.desired),
|
|
126
|
+
lifecycle: text(tab.lifecycle),
|
|
127
|
+
incarnation: tab.incarnation ?? null,
|
|
128
|
+
holderPid: tab.holderPid ?? null,
|
|
129
|
+
childPid: tab.childPid ?? null,
|
|
130
|
+
release: text(tab.release) === null ? null : redact(text(tab.release)),
|
|
131
|
+
lastExit: text(tab.lastExit),
|
|
132
|
+
restarts: tab.restarts ?? null,
|
|
133
|
+
};
|
|
134
|
+
}),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async function redactedMarker(path, redact) {
|
|
138
|
+
try {
|
|
139
|
+
const marker = JSON.parse(await readFile(path, "utf8"));
|
|
140
|
+
return {
|
|
141
|
+
present: true,
|
|
142
|
+
pid: marker.pid ?? null,
|
|
143
|
+
generation: marker.generation ?? null,
|
|
144
|
+
verified: marker.verified ?? null,
|
|
145
|
+
detachmentMode: marker.detachmentMode ?? null,
|
|
146
|
+
releaseRoot: typeof marker.releaseRoot === "string" ? redact(marker.releaseRoot) : null,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return { present: false };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function artifactFacts(roots, platform, architecture, redact) {
|
|
154
|
+
const facts = [];
|
|
155
|
+
for (const root of roots) {
|
|
156
|
+
const executable = resolveTerminalHostPath(root, {}, platform, architecture);
|
|
157
|
+
const manifest = await readFile(resolve(dirname(executable), TERMINAL_HOST_MANIFEST_FILENAME), "utf8")
|
|
158
|
+
.then(text => JSON.parse(text))
|
|
159
|
+
.catch(() => null);
|
|
160
|
+
const verification = await verifyTerminalHostArtifact(executable, platform, architecture)
|
|
161
|
+
.then(() => "verified")
|
|
162
|
+
.catch((error) => error instanceof Error && "code" in error ? String(error.code) : "failed");
|
|
163
|
+
facts.push({ releaseRoot: redact(root), verification, manifest });
|
|
164
|
+
}
|
|
165
|
+
return facts;
|
|
166
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type TerminalHostFallbackReason = "unsupported platform" | "terminal host not installed" | "host probe failed" | "incompatible generation" | "registry unreadable" | "recovery blocked" | "launch not verified" | "host startup timed out";
|
|
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
2
|
export type EnsureTerminalHostResult = {
|
|
3
3
|
readonly status: "ready";
|
|
4
4
|
readonly executable: string;
|
|
@@ -24,6 +24,7 @@ export interface EnsureTerminalHostOptions {
|
|
|
24
24
|
readonly maxConcurrentStarts?: number;
|
|
25
25
|
readonly unresponsiveRestartSeconds?: number;
|
|
26
26
|
readonly run?: (executable: string, arguments_: readonly string[], environment: NodeJS.ProcessEnv, timeoutMs: number) => Promise<HostCommandResult>;
|
|
27
|
+
readonly verify?: (executable: string, platform: NodeJS.Platform, architecture: string) => Promise<unknown>;
|
|
27
28
|
}
|
|
28
29
|
export declare function resolveTerminalHostPath(releaseRoot: string, environment?: NodeJS.ProcessEnv, platform?: NodeJS.Platform, architecture?: string): string;
|
|
29
30
|
export declare function ensureTerminalHost(options: EnsureTerminalHostOptions): Promise<EnsureTerminalHostResult>;
|
|
@@ -3,24 +3,41 @@ import { access } from "node:fs/promises";
|
|
|
3
3
|
import { constants } from "node:fs";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
import { PRODUCT_IDENTITY } from "../../product-identity.js";
|
|
6
|
+
import { TerminalHostArtifactError, verifyTerminalHostArtifact } from "./terminal-host-artifact.js";
|
|
6
7
|
export function resolveTerminalHostPath(releaseRoot, environment = process.env, platform = process.platform, architecture = process.arch) {
|
|
7
|
-
const override = environment
|
|
8
|
+
const override = developmentOverride(environment);
|
|
8
9
|
if (override)
|
|
9
10
|
return resolve(override);
|
|
10
|
-
const executable = platform === "win32" ?
|
|
11
|
+
const executable = platform === "win32" ? `${PRODUCT_IDENTITY.artifacts.nativeExecutable}.exe` : PRODUCT_IDENTITY.artifacts.nativeExecutable;
|
|
11
12
|
return resolve(releaseRoot, "dist", "native", `${platform}-${architecture}`, executable);
|
|
12
13
|
}
|
|
14
|
+
/** The terminal-host override is a development aid; installed releases always run their own verified artifact. */
|
|
15
|
+
function developmentOverride(environment) {
|
|
16
|
+
if (!environment[PRODUCT_IDENTITY.environment.developmentInstanceId])
|
|
17
|
+
return undefined;
|
|
18
|
+
return environment[PRODUCT_IDENTITY.environment.terminalHostPath]?.trim() || undefined;
|
|
19
|
+
}
|
|
13
20
|
export async function ensureTerminalHost(options) {
|
|
14
21
|
const platform = options.platform ?? process.platform;
|
|
15
22
|
const architecture = options.architecture ?? process.arch;
|
|
16
23
|
if (!supported(platform, architecture))
|
|
17
24
|
return { status: "fallback", reason: "unsupported platform" };
|
|
18
25
|
const executable = resolveTerminalHostPath(options.releaseRoot, options.environment, platform, architecture);
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
if (developmentOverride(options.environment)) {
|
|
27
|
+
try {
|
|
28
|
+
await access(executable, constants.R_OK | (platform === "win32" ? 0 : constants.X_OK));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return { status: "fallback", reason: "terminal host not installed" };
|
|
32
|
+
}
|
|
21
33
|
}
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
else {
|
|
35
|
+
try {
|
|
36
|
+
await (options.verify ?? verifyTerminalHostArtifact)(executable, platform, architecture);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
return { status: "fallback", reason: verificationReason(error) };
|
|
40
|
+
}
|
|
24
41
|
}
|
|
25
42
|
const run = options.run ?? runHostCommand;
|
|
26
43
|
const timeoutMs = options.timeoutMs ?? 8_000;
|
|
@@ -79,7 +96,16 @@ export async function ensureTerminalHost(options) {
|
|
|
79
96
|
}
|
|
80
97
|
function supported(platform, architecture) {
|
|
81
98
|
return (platform === "win32" || platform === "linux") && architecture === "x64"
|
|
82
|
-
|| platform === "darwin" && architecture === "arm64";
|
|
99
|
+
|| platform === "darwin" && (architecture === "arm64" || architecture === "x64");
|
|
100
|
+
}
|
|
101
|
+
function verificationReason(error) {
|
|
102
|
+
if (!(error instanceof TerminalHostArtifactError))
|
|
103
|
+
return "terminal host not verified";
|
|
104
|
+
if (error.code === "TERMINAL_HOST_ARTIFACT_MISSING")
|
|
105
|
+
return "terminal host not installed";
|
|
106
|
+
if (error.code === "TERMINAL_HOST_ARTIFACT_UNSUPPORTED")
|
|
107
|
+
return "unsupported platform";
|
|
108
|
+
return "terminal host not verified";
|
|
83
109
|
}
|
|
84
110
|
function parseResult(output) {
|
|
85
111
|
const line = output.trim().split(/\r?\n/).filter(Boolean).at(-1);
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export { ensureTerminalHost, resolveTerminalHostPath } from "./ensure-terminal-host.js";
|
|
2
2
|
export type { EnsureTerminalHostOptions, EnsureTerminalHostResult, TerminalHostFallbackReason } from "./ensure-terminal-host.js";
|
|
3
|
-
export { readResidentTabLaunchSettings
|
|
3
|
+
export { readResidentTabLaunchSettings } from "./settings.js";
|
|
4
4
|
export type { ResidentTabLaunchSettings } from "./settings.js";
|
|
5
5
|
export { selectResidentHost } from "./routing.js";
|
|
6
6
|
export type { ResidentHostSelectionOptions } from "./routing.js";
|
|
7
|
+
export { readResidentReleaseHolds } from "./release-holds.js";
|
|
8
|
+
export type { ResidentReleaseHoldReport } from "./release-holds.js";
|
|
9
|
+
export { runTerminalHostMaintenance } from "./maintenance.js";
|
|
10
|
+
export type { MaintenanceAction, MaintenanceHostState, MaintenanceOptions, MaintenanceReport } from "./maintenance.js";
|
|
11
|
+
export { DOCTOR_MAX_BYTES, writeDoctorBundle } from "./doctor.js";
|
|
12
|
+
export type { DoctorBundle, DoctorOptions } from "./doctor.js";
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export { ensureTerminalHost, resolveTerminalHostPath } from "./ensure-terminal-host.js";
|
|
2
|
-
export { readResidentTabLaunchSettings
|
|
2
|
+
export { readResidentTabLaunchSettings } from "./settings.js";
|
|
3
3
|
export { selectResidentHost } from "./routing.js";
|
|
4
|
+
export { readResidentReleaseHolds } from "./release-holds.js";
|
|
5
|
+
export { runTerminalHostMaintenance } from "./maintenance.js";
|
|
6
|
+
export { DOCTOR_MAX_BYTES, writeDoctorBundle } from "./doctor.js";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type MaintenanceAction = "list" | "stop" | "stop-all" | "host-status" | "host-stop";
|
|
2
|
+
export type MaintenanceHostState = "none" | "running" | "stopped" | "unresponsive" | "incompatible" | "refused" | "unverifiable" | "unavailable";
|
|
3
|
+
/** The versioned `a1 tabs` report: `a1-tabs-maintenance-v1`, as printed by the terminal host. */
|
|
4
|
+
export interface MaintenanceReport {
|
|
5
|
+
readonly schema: "a1-tabs-maintenance-v1";
|
|
6
|
+
readonly command: string;
|
|
7
|
+
readonly host: MaintenanceHostState;
|
|
8
|
+
readonly stateDir?: string;
|
|
9
|
+
readonly result?: Record<string, unknown>;
|
|
10
|
+
readonly pid?: number;
|
|
11
|
+
readonly message?: string;
|
|
12
|
+
readonly releaseRoot?: string | null;
|
|
13
|
+
readonly terminatedUnresponsive?: number;
|
|
14
|
+
/** The terminal host's exit status: 0 success, 3 refused or failed, 4 another generation. */
|
|
15
|
+
readonly exitCode: number;
|
|
16
|
+
}
|
|
17
|
+
export interface MaintenanceOptions {
|
|
18
|
+
readonly action: MaintenanceAction;
|
|
19
|
+
readonly tabId?: string;
|
|
20
|
+
readonly profileRoot: string;
|
|
21
|
+
readonly dataDir: string;
|
|
22
|
+
readonly runtimeDir: string;
|
|
23
|
+
/** Installed release roots whose verified terminal host may run maintenance, in preference order. */
|
|
24
|
+
readonly releaseRoots: readonly string[];
|
|
25
|
+
readonly environment: NodeJS.ProcessEnv;
|
|
26
|
+
readonly platform?: NodeJS.Platform;
|
|
27
|
+
readonly architecture?: string;
|
|
28
|
+
readonly timeoutMs?: number;
|
|
29
|
+
readonly verify?: (executable: string, platform: NodeJS.Platform, architecture: string) => Promise<unknown>;
|
|
30
|
+
readonly run?: (executable: string, arguments_: readonly string[], timeoutMs: number) => Promise<{
|
|
31
|
+
readonly code: number;
|
|
32
|
+
readonly stdout: string;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Runs one maintenance form through a verified terminal host. When the running host speaks
|
|
37
|
+
* another protocol generation, it retries once with the binary from the release that host
|
|
38
|
+
* recorded, after verifying that release's manifest; it never runs an unverified binary.
|
|
39
|
+
*/
|
|
40
|
+
export declare function runTerminalHostMaintenance(options: MaintenanceOptions): Promise<MaintenanceReport>;
|
|
41
|
+
/** The largest maintenance report accepted from the terminal host. */
|
|
42
|
+
export declare const MAX_MAINTENANCE_REPORT_BYTES: number;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
|
+
import { PRODUCT_IDENTITY } from "../../product-identity.js";
|
|
4
|
+
import { runBoundedHostCommand } from "./bounded-host-command.js";
|
|
5
|
+
import { resolveTerminalHostPath } from "./ensure-terminal-host.js";
|
|
6
|
+
import { verifyTerminalHostArtifact } from "./terminal-host-artifact.js";
|
|
7
|
+
const INCOMPATIBLE_EXIT = 4;
|
|
8
|
+
/**
|
|
9
|
+
* Runs one maintenance form through a verified terminal host. When the running host speaks
|
|
10
|
+
* another protocol generation, it retries once with the binary from the release that host
|
|
11
|
+
* recorded, after verifying that release's manifest; it never runs an unverified binary.
|
|
12
|
+
*/
|
|
13
|
+
export async function runTerminalHostMaintenance(options) {
|
|
14
|
+
const platform = options.platform ?? process.platform;
|
|
15
|
+
const architecture = options.architecture ?? process.arch;
|
|
16
|
+
const executable = await firstVerifiedHost(options, options.releaseRoots, platform, architecture);
|
|
17
|
+
if (executable === undefined)
|
|
18
|
+
return unavailable(options.action, "no verified terminal host is installed");
|
|
19
|
+
const report = await invoke(options, executable);
|
|
20
|
+
if (report.exitCode !== INCOMPATIBLE_EXIT || typeof report.releaseRoot !== "string")
|
|
21
|
+
return report;
|
|
22
|
+
const cohort = await firstVerifiedHost({ ...options, environment: {} }, [report.releaseRoot], platform, architecture);
|
|
23
|
+
if (cohort === undefined || cohort === executable) {
|
|
24
|
+
return { ...report, message: "the running tabs use another version whose terminal host cannot be verified" };
|
|
25
|
+
}
|
|
26
|
+
return await invoke(options, cohort);
|
|
27
|
+
}
|
|
28
|
+
function arguments_(options) {
|
|
29
|
+
const command = options.action === "stop" ? ["stop", options.tabId ?? ""] : [options.action];
|
|
30
|
+
return ["tabs", ...command, "--profile", options.profileRoot, "--data-dir", options.dataDir, "--runtime-dir", options.runtimeDir];
|
|
31
|
+
}
|
|
32
|
+
async function invoke(options, executable) {
|
|
33
|
+
const timeoutMs = options.timeoutMs ?? 120_000;
|
|
34
|
+
try {
|
|
35
|
+
const result = await (options.run ?? runHost)(executable, arguments_(options), timeoutMs);
|
|
36
|
+
const line = result.stdout.trim().split(/\r?\n/u).at(-1) ?? "";
|
|
37
|
+
const parsed = JSON.parse(line);
|
|
38
|
+
if (parsed.schema !== "a1-tabs-maintenance-v1" || typeof parsed.host !== "string")
|
|
39
|
+
throw new Error("malformed maintenance report");
|
|
40
|
+
return { ...parsed, exitCode: result.code };
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return unavailable(options.action, `terminal host maintenance failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function firstVerifiedHost(options, roots, platform, architecture) {
|
|
47
|
+
const override = options.environment[PRODUCT_IDENTITY.environment.developmentInstanceId]
|
|
48
|
+
? options.environment[PRODUCT_IDENTITY.environment.terminalHostPath]?.trim()
|
|
49
|
+
: undefined;
|
|
50
|
+
if (override) {
|
|
51
|
+
try {
|
|
52
|
+
await access(override, constants.R_OK | (platform === "win32" ? 0 : constants.X_OK));
|
|
53
|
+
return override;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const root of roots) {
|
|
60
|
+
const candidate = resolveTerminalHostPath(root, {}, platform, architecture);
|
|
61
|
+
try {
|
|
62
|
+
await (options.verify ?? verifyTerminalHostArtifact)(candidate, platform, architecture);
|
|
63
|
+
return candidate;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Rationale: an older retained release may predate the packaged host; try the next one.
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
function unavailable(action, message) {
|
|
72
|
+
return { schema: "a1-tabs-maintenance-v1", command: action, host: "unavailable", message, exitCode: 3 };
|
|
73
|
+
}
|
|
74
|
+
/** The largest maintenance report accepted from the terminal host. */
|
|
75
|
+
export const MAX_MAINTENANCE_REPORT_BYTES = 4 * 1024 * 1024;
|
|
76
|
+
async function runHost(executable, arguments_, timeoutMs) {
|
|
77
|
+
return await runBoundedHostCommand(executable, arguments_, { maxStdoutBytes: MAX_MAINTENANCE_REPORT_BYTES, timeoutMs, label: "terminal host maintenance" });
|
|
78
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface ResidentReleaseHoldReport {
|
|
2
|
+
/** False when any resident record or identity could not be verified; callers then keep every release. */
|
|
3
|
+
readonly complete: boolean;
|
|
4
|
+
readonly releaseRoots: readonly string[];
|
|
5
|
+
readonly uncertain: readonly string[];
|
|
6
|
+
}
|
|
7
|
+
export interface ResidentReleaseHoldOptions {
|
|
8
|
+
readonly dataDir: string;
|
|
9
|
+
/** Installed release roots whose terminal host may answer, in preference order. */
|
|
10
|
+
readonly releaseRoots: readonly string[];
|
|
11
|
+
readonly platform?: NodeJS.Platform;
|
|
12
|
+
readonly architecture?: string;
|
|
13
|
+
readonly timeoutMs?: number;
|
|
14
|
+
readonly verify?: (executable: string, platform: NodeJS.Platform, architecture: string) => Promise<unknown>;
|
|
15
|
+
readonly run?: (executable: string, arguments_: readonly string[], timeoutMs: number) => Promise<{
|
|
16
|
+
readonly code: number;
|
|
17
|
+
readonly stdout: string;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Reports the releases that live resident processes run from, read-only. The verified terminal
|
|
22
|
+
* host inspects its own records, so pid reuse and identity formats are judged exactly as the
|
|
23
|
+
* server judges them. No records means nothing to hold; any doubt yields an incomplete report.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readResidentReleaseHolds(options: ResidentReleaseHoldOptions): Promise<ResidentReleaseHoldReport>;
|
|
26
|
+
/** The largest release-hold report accepted from the terminal host. */
|
|
27
|
+
export declare const MAX_RELEASE_HOLD_REPORT_BYTES: number;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { runBoundedHostCommand } from "./bounded-host-command.js";
|
|
4
|
+
import { resolveTerminalHostPath } from "./ensure-terminal-host.js";
|
|
5
|
+
import { verifyTerminalHostArtifact } from "./terminal-host-artifact.js";
|
|
6
|
+
const COMPLETE_EMPTY = Object.freeze({ complete: true, releaseRoots: Object.freeze([]), uncertain: Object.freeze([]) });
|
|
7
|
+
/**
|
|
8
|
+
* Reports the releases that live resident processes run from, read-only. The verified terminal
|
|
9
|
+
* host inspects its own records, so pid reuse and identity formats are judged exactly as the
|
|
10
|
+
* server judges them. No records means nothing to hold; any doubt yields an incomplete report.
|
|
11
|
+
*/
|
|
12
|
+
export async function readResidentReleaseHolds(options) {
|
|
13
|
+
if (!await hasResidentRecords(options.dataDir))
|
|
14
|
+
return COMPLETE_EMPTY;
|
|
15
|
+
const platform = options.platform ?? process.platform;
|
|
16
|
+
const architecture = options.architecture ?? process.arch;
|
|
17
|
+
const verify = options.verify ?? verifyTerminalHostArtifact;
|
|
18
|
+
let executable;
|
|
19
|
+
for (const root of options.releaseRoots) {
|
|
20
|
+
const candidate = resolveTerminalHostPath(root, {}, platform, architecture);
|
|
21
|
+
try {
|
|
22
|
+
await verify(candidate, platform, architecture);
|
|
23
|
+
executable = candidate;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Rationale: an older release may predate the packaged host; another retained release may still carry it.
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (!executable)
|
|
31
|
+
return incomplete("no verified terminal host is installed to inspect resident records");
|
|
32
|
+
try {
|
|
33
|
+
const result = await (options.run ?? runHost)(executable, ["release-holds", "--data-dir", options.dataDir], options.timeoutMs ?? 5_000);
|
|
34
|
+
const report = JSON.parse(result.stdout.trim().split(/\r?\n/u).at(-1) ?? "");
|
|
35
|
+
if (result.code !== 0 || report.schema !== "a1-resident-release-holds-v1" || typeof report.complete !== "boolean"
|
|
36
|
+
|| !isStringArray(report.releaseRoots) || !isStringArray(report.uncertain)) {
|
|
37
|
+
return incomplete("terminal host returned no valid release-hold report");
|
|
38
|
+
}
|
|
39
|
+
return { complete: report.complete, releaseRoots: report.releaseRoots, uncertain: report.uncertain };
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return incomplete("terminal host release-hold inspection failed");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function hasResidentRecords(dataDir) {
|
|
46
|
+
let profiles;
|
|
47
|
+
try {
|
|
48
|
+
profiles = await readdir(resolve(dataDir, "tabs"));
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
return !(error instanceof Error && "code" in error && error.code === "ENOENT");
|
|
52
|
+
}
|
|
53
|
+
for (const profile of profiles) {
|
|
54
|
+
for (const record of ["server.json", "registry.json"]) {
|
|
55
|
+
try {
|
|
56
|
+
await stat(resolve(dataDir, "tabs", profile, record));
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
function incomplete(reason) {
|
|
68
|
+
return { complete: false, releaseRoots: [], uncertain: [reason] };
|
|
69
|
+
}
|
|
70
|
+
function isStringArray(value) {
|
|
71
|
+
return Array.isArray(value) && value.every(item => typeof item === "string");
|
|
72
|
+
}
|
|
73
|
+
/** The largest release-hold report accepted from the terminal host. */
|
|
74
|
+
export const MAX_RELEASE_HOLD_REPORT_BYTES = 1024 * 1024;
|
|
75
|
+
async function runHost(executable, arguments_, timeoutMs) {
|
|
76
|
+
return await runBoundedHostCommand(executable, arguments_, { maxStdoutBytes: MAX_RELEASE_HOLD_REPORT_BYTES, timeoutMs, label: "terminal host release-hold inspection" });
|
|
77
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { EnsureTerminalHostResult } from "./ensure-terminal-host.js";
|
|
2
2
|
export interface ResidentHostSelectionOptions {
|
|
3
3
|
readonly profileId: "a1" | "pi";
|
|
4
|
-
readonly hasSessionSelection: boolean;
|
|
5
4
|
readonly currentLaunchContract: boolean;
|
|
6
|
-
readonly
|
|
5
|
+
readonly solo: boolean;
|
|
6
|
+
/** Both standard input and standard output are terminals the attach client can own. */
|
|
7
|
+
readonly interactive: boolean;
|
|
7
8
|
readonly ensureHost: () => Promise<EnsureTerminalHostResult>;
|
|
8
9
|
}
|
|
9
|
-
/** Invariant:
|
|
10
|
+
/** Invariant: comparison, solo, and superseded launches stay outside resident discovery and startup. */
|
|
10
11
|
export declare function selectResidentHost(options: ResidentHostSelectionOptions): Promise<EnsureTerminalHostResult | null>;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
/** Invariant:
|
|
1
|
+
/** Invariant: comparison, solo, and superseded launches stay outside resident discovery and startup. */
|
|
2
2
|
export async function selectResidentHost(options) {
|
|
3
|
-
if (options.profileId !== "a1" || options.
|
|
4
|
-
return null;
|
|
5
|
-
if (!await options.readSetting())
|
|
3
|
+
if (options.profileId !== "a1" || options.solo || !options.currentLaunchContract)
|
|
6
4
|
return null;
|
|
5
|
+
// Platform: the attach client reads the outer terminal's size and owns its raw input, so piped
|
|
6
|
+
// or redirected launches run the direct UI, which already serves non-terminal streams.
|
|
7
|
+
if (!options.interactive)
|
|
8
|
+
return { status: "fallback", reason: "no interactive terminal" };
|
|
7
9
|
return await options.ensureHost();
|
|
8
10
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
export declare const RESIDENT_TABS_MAX_CONCURRENT_STARTS = 2;
|
|
2
|
+
export declare const RESIDENT_TABS_MAX_CONCURRENT_STARTS_CAP = 8;
|
|
3
|
+
export declare const RESIDENT_TABS_PREWARM_COUNT = 1;
|
|
4
|
+
export declare const RESIDENT_TABS_PREWARM_CAP = 1;
|
|
1
5
|
export interface ResidentTabLaunchSettings {
|
|
2
|
-
readonly
|
|
6
|
+
readonly tabsMax: number;
|
|
7
|
+
readonly tabsAutoName: boolean;
|
|
3
8
|
readonly maxConcurrentStarts: number;
|
|
9
|
+
readonly prewarmCount: number;
|
|
4
10
|
readonly unresponsiveRestartSeconds: number;
|
|
5
11
|
readonly stallNoticeSeconds: number;
|
|
6
12
|
}
|
|
7
13
|
/** Read only resident-launch values before any UI or extension module is loaded. */
|
|
8
14
|
export declare function readResidentTabLaunchSettings(profileDir: string, namespace?: string, legacyConfigDir?: string, profileId?: string): Promise<ResidentTabLaunchSettings>;
|
|
9
|
-
export declare function readResidentTabsSetting(profileDir: string, namespace?: string, legacyConfigDir?: string, profileId?: string): Promise<boolean>;
|
|
@@ -2,9 +2,15 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { OWNED_SETTING_DECLARATIONS, OWNED_UI_SETTINGS_MIGRATIONS, OWNED_UI_SETTINGS_VERSION, normalizeOwnedUiSettingsValues, parseOwnedUiSettingsDocument, readProfileSettingsNamespace, repairProfileSettingsDocument, resolveOwnedUiSettings, settingValue, } from "../../ui/settings/index.js";
|
|
4
4
|
const MAX_SETTINGS_BYTES = 256 * 1024;
|
|
5
|
+
export const RESIDENT_TABS_MAX_CONCURRENT_STARTS = 2;
|
|
6
|
+
export const RESIDENT_TABS_MAX_CONCURRENT_STARTS_CAP = 8;
|
|
7
|
+
export const RESIDENT_TABS_PREWARM_COUNT = 1;
|
|
8
|
+
export const RESIDENT_TABS_PREWARM_CAP = 1;
|
|
5
9
|
const DEFAULTS = Object.freeze({
|
|
6
|
-
|
|
7
|
-
|
|
10
|
+
tabsMax: 10,
|
|
11
|
+
tabsAutoName: true,
|
|
12
|
+
maxConcurrentStarts: RESIDENT_TABS_MAX_CONCURRENT_STARTS,
|
|
13
|
+
prewarmCount: RESIDENT_TABS_PREWARM_COUNT,
|
|
8
14
|
unresponsiveRestartSeconds: 120,
|
|
9
15
|
stallNoticeSeconds: 300,
|
|
10
16
|
});
|
|
@@ -38,28 +44,23 @@ export async function readResidentTabLaunchSettings(profileDir, namespace = "a1"
|
|
|
38
44
|
return DEFAULTS;
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
|
-
export async function readResidentTabsSetting(profileDir, namespace = "a1", legacyConfigDir, profileId = namespace) {
|
|
42
|
-
return (await readResidentTabLaunchSettings(profileDir, namespace, legacyConfigDir, profileId)).residentTabs;
|
|
43
|
-
}
|
|
44
47
|
function resolveLaunchValues(document, migrations) {
|
|
45
48
|
const resolution = resolveOwnedUiSettings({
|
|
46
49
|
declarations: [
|
|
47
|
-
OWNED_SETTING_DECLARATIONS.
|
|
48
|
-
OWNED_SETTING_DECLARATIONS.
|
|
49
|
-
OWNED_SETTING_DECLARATIONS.tabsUnresponsiveRestartSeconds,
|
|
50
|
-
OWNED_SETTING_DECLARATIONS.tabsStallNoticeSeconds,
|
|
50
|
+
OWNED_SETTING_DECLARATIONS.tabsMax,
|
|
51
|
+
OWNED_SETTING_DECLARATIONS.tabsAutoName,
|
|
51
52
|
],
|
|
52
53
|
migrations,
|
|
53
54
|
document,
|
|
54
55
|
currentVersion: OWNED_UI_SETTINGS_VERSION,
|
|
55
56
|
});
|
|
56
|
-
const
|
|
57
|
-
const unresponsive = settingValue(resolution, "tabsUnresponsiveRestartSeconds");
|
|
58
|
-
const stall = settingValue(resolution, "tabsStallNoticeSeconds");
|
|
57
|
+
const tabsMax = document.values?.["tabsMax"] ?? settingValue(resolution, "tabsMax");
|
|
59
58
|
return {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
tabsMax: typeof tabsMax === "number" && Number.isFinite(tabsMax) ? Math.max(1, Math.min(50, Math.trunc(tabsMax))) : DEFAULTS.tabsMax,
|
|
60
|
+
tabsAutoName: settingValue(resolution, "tabsAutoName") === true,
|
|
61
|
+
maxConcurrentStarts: RESIDENT_TABS_MAX_CONCURRENT_STARTS,
|
|
62
|
+
prewarmCount: RESIDENT_TABS_PREWARM_COUNT,
|
|
63
|
+
unresponsiveRestartSeconds: DEFAULTS.unresponsiveRestartSeconds,
|
|
64
|
+
stallNoticeSeconds: DEFAULTS.stallNoticeSeconds,
|
|
64
65
|
};
|
|
65
66
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const TERMINAL_HOST_MANIFEST_FILENAME = "terminal-host.manifest.json";
|
|
2
|
+
export declare const TERMINAL_HOST_GENERATION = 1;
|
|
3
|
+
export type TerminalHostArtifactCode = "TERMINAL_HOST_ARTIFACT_MISSING" | "TERMINAL_HOST_ARTIFACT_INCOMPATIBLE" | "TERMINAL_HOST_ARTIFACT_UNSUPPORTED" | "TERMINAL_HOST_ARTIFACT_TAMPERED";
|
|
4
|
+
export interface VerifiedTerminalHostArtifact {
|
|
5
|
+
readonly executable: string;
|
|
6
|
+
readonly sessionLock: string;
|
|
7
|
+
readonly generation: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class TerminalHostArtifactError extends Error {
|
|
10
|
+
readonly code: TerminalHostArtifactCode;
|
|
11
|
+
constructor(message: string, code: TerminalHostArtifactCode);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Verifies a release's terminal host and its session-writer lock against their manifest before
|
|
15
|
+
* anything executes or loads them: schema, generation, platform, architecture, file names, sizes,
|
|
16
|
+
* and SHA-256 digests.
|
|
17
|
+
*/
|
|
18
|
+
export declare function verifyTerminalHostArtifact(executable: string, platform?: NodeJS.Platform, architecture?: string, generation?: number): Promise<VerifiedTerminalHostArtifact>;
|