@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
|
@@ -15,16 +15,21 @@ export class TabBridgeClient {
|
|
|
15
15
|
#latestStatus = null;
|
|
16
16
|
#accent = null;
|
|
17
17
|
#latestSession = null;
|
|
18
|
+
#latestShortcuts = null;
|
|
18
19
|
#heartbeat = null;
|
|
19
20
|
#pending = new Map();
|
|
20
21
|
#startWaiter = null;
|
|
21
22
|
#commandListeners = new Set();
|
|
23
|
+
#pendingCommands = [];
|
|
22
24
|
#boundaryInputRemainder = "";
|
|
23
25
|
constructor(options) { this.#options = options; }
|
|
26
|
+
get tabId() { return this.#options.credentials.tabId; }
|
|
24
27
|
get available() { return this.#available && this.#connected; }
|
|
25
28
|
captureAttachment() { return this.#attachment === null ? null : { ...this.#attachment }; }
|
|
26
29
|
onCommand(listener) {
|
|
27
30
|
this.#commandListeners.add(listener);
|
|
31
|
+
for (const command of this.#pendingCommands.splice(0))
|
|
32
|
+
listener(command);
|
|
28
33
|
return () => this.#commandListeners.delete(listener);
|
|
29
34
|
}
|
|
30
35
|
async start(timeoutMs = 4_000) {
|
|
@@ -63,7 +68,7 @@ export class TabBridgeClient {
|
|
|
63
68
|
transport.once("connect", () => {
|
|
64
69
|
this.#send("hello", {
|
|
65
70
|
role: "bridge", generation: 1, build: this.#options.build ?? "a1",
|
|
66
|
-
features: ["bridge-status", "bridge-detach", "bridge-heartbeat", "bridge-restart", "bridge-visibility", "bridge-rename", "bridge-stop", "controller-boundary", "recovery-journal"],
|
|
71
|
+
features: ["bridge-status", "bridge-detach", "bridge-heartbeat", "bridge-restart", "bridge-visibility", "bridge-rename", "bridge-route", "bridge-stop", "bridge-shortcuts", "controller-boundary", "recovery-journal"],
|
|
67
72
|
token: [...Buffer.from(this.#options.credentials.token, "base64")], tabId: this.#options.credentials.tabId,
|
|
68
73
|
pid: this.#options.pid ?? process.pid, startIdentity: this.#options.startIdentity ?? `${process.pid}:node`,
|
|
69
74
|
}, ++this.#request);
|
|
@@ -102,12 +107,25 @@ export class TabBridgeClient {
|
|
|
102
107
|
prompt(interrupted) {
|
|
103
108
|
this.#send("bridge.prompt", { interrupted });
|
|
104
109
|
}
|
|
110
|
+
/** Publishes the effective tab shortcut assignments so the host dispatches and labels the same keys. */
|
|
111
|
+
shortcuts(assignments) {
|
|
112
|
+
this.#latestShortcuts = Object.entries(assignments).map(([action, key]) => [action, key]);
|
|
113
|
+
this.#sendShortcuts();
|
|
114
|
+
}
|
|
115
|
+
#sendShortcuts() {
|
|
116
|
+
if (this.#latestShortcuts === null || !this.#supports("bridge-shortcuts"))
|
|
117
|
+
return;
|
|
118
|
+
this.#send("bridge.shortcuts", { assignments: this.#latestShortcuts.map(([action, key]) => [action, key]) });
|
|
119
|
+
}
|
|
105
120
|
requestDetach(origin = this.captureAttachment(), timeoutMs = 1_000) {
|
|
106
121
|
return this.#requestAction("detach", origin, timeoutMs);
|
|
107
122
|
}
|
|
108
123
|
requestRestart(origin = this.captureAttachment(), timeoutMs = 1_000) {
|
|
109
124
|
return this.#requestAction("restart", origin, timeoutMs);
|
|
110
125
|
}
|
|
126
|
+
requestClose(origin = this.captureAttachment(), timeoutMs = 1_000) {
|
|
127
|
+
return this.#requestAction("close", origin, timeoutMs);
|
|
128
|
+
}
|
|
111
129
|
consumeControllerBoundaryInput(data) {
|
|
112
130
|
const prefix = "\x1bP+a1;controller=";
|
|
113
131
|
const suffix = "\x1b\\";
|
|
@@ -165,6 +183,12 @@ export class TabBridgeClient {
|
|
|
165
183
|
this.#fail(false);
|
|
166
184
|
}
|
|
167
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
|
+
}
|
|
168
192
|
for (const listener of this.#commandListeners)
|
|
169
193
|
listener(command);
|
|
170
194
|
}
|
|
@@ -191,6 +215,7 @@ export class TabBridgeClient {
|
|
|
191
215
|
this.status(this.#latestStatus.state, this.#latestStatus.degraded, this.#latestStatus.pendingRequests, this.#latestStatus.queuedInput);
|
|
192
216
|
if (this.#latestSession !== null)
|
|
193
217
|
this.#send("bridge.session", this.#latestSession);
|
|
218
|
+
this.#sendShortcuts();
|
|
194
219
|
return;
|
|
195
220
|
}
|
|
196
221
|
if (message.type === "incompatible-generation") {
|
|
@@ -206,6 +231,10 @@ export class TabBridgeClient {
|
|
|
206
231
|
this.#emitCommand({ type: "rename", name: message.body["name"], nameSource: source === "auto" || source === "default" ? source : "user" });
|
|
207
232
|
return;
|
|
208
233
|
}
|
|
234
|
+
if (message.type === "bridge.route" && message.body["route"] === "settings") {
|
|
235
|
+
this.#emitCommand({ type: "route", route: "settings" });
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
209
238
|
if (message.type === "bridge.stop" && typeof message.body["deadlineMs"] === "number") {
|
|
210
239
|
this.#emitCommand({ type: "stop", deadlineMs: message.body["deadlineMs"] });
|
|
211
240
|
return;
|
|
@@ -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. */
|
|
@@ -2,37 +2,38 @@ import { constants } from "node:fs";
|
|
|
2
2
|
import { access } from "node:fs/promises";
|
|
3
3
|
import { PRODUCT_IDENTITY } from "../../product-identity.js";
|
|
4
4
|
import { runBoundedHostCommand } from "./bounded-host-command.js";
|
|
5
|
-
import { resolveTerminalHostPath } from "./ensure-terminal-host.js";
|
|
5
|
+
import { resolveTerminalHostEntryPath, resolveTerminalHostPath } from "./ensure-terminal-host.js";
|
|
6
6
|
import { verifyTerminalHostArtifact } from "./terminal-host-artifact.js";
|
|
7
7
|
const INCOMPATIBLE_EXIT = 4;
|
|
8
8
|
/**
|
|
9
9
|
* Runs one maintenance form through a verified terminal host. When the running host speaks
|
|
10
|
-
* another protocol generation, it retries once with the
|
|
11
|
-
* recorded, after verifying that release's manifest; it never
|
|
10
|
+
* another protocol generation, it retries once with the addon from the release that host
|
|
11
|
+
* recorded, after verifying that release's manifest; it never loads an unverified addon.
|
|
12
12
|
*/
|
|
13
13
|
export async function runTerminalHostMaintenance(options) {
|
|
14
14
|
const platform = options.platform ?? process.platform;
|
|
15
15
|
const architecture = options.architecture ?? process.arch;
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
16
|
+
const command = await firstVerifiedHost(options, options.releaseRoots, platform, architecture);
|
|
17
|
+
if (command === undefined)
|
|
18
18
|
return unavailable(options.action, "no verified terminal host is installed");
|
|
19
|
-
const report = await invoke(options,
|
|
19
|
+
const report = await invoke(options, command);
|
|
20
20
|
if (report.exitCode !== INCOMPATIBLE_EXIT || typeof report.releaseRoot !== "string")
|
|
21
21
|
return report;
|
|
22
22
|
const cohort = await firstVerifiedHost({ ...options, environment: {} }, [report.releaseRoot], platform, architecture);
|
|
23
|
-
if (cohort === undefined || cohort ===
|
|
23
|
+
if (cohort === undefined || cohort.entry === command.entry) {
|
|
24
24
|
return { ...report, message: "the running tabs use another version whose terminal host cannot be verified" };
|
|
25
25
|
}
|
|
26
|
-
return await invoke(options, cohort);
|
|
26
|
+
return await invoke({ ...options, environment: installedEnvironment(options.environment) }, cohort);
|
|
27
27
|
}
|
|
28
28
|
function arguments_(options) {
|
|
29
29
|
const command = options.action === "stop" ? ["stop", options.tabId ?? ""] : [options.action];
|
|
30
30
|
return ["tabs", ...command, "--profile", options.profileRoot, "--data-dir", options.dataDir, "--runtime-dir", options.runtimeDir];
|
|
31
31
|
}
|
|
32
|
-
async function invoke(options,
|
|
32
|
+
async function invoke(options, command) {
|
|
33
33
|
const timeoutMs = options.timeoutMs ?? 120_000;
|
|
34
34
|
try {
|
|
35
|
-
const
|
|
35
|
+
const run = options.run ?? ((host, argumentsValue, limit) => runHost(host, argumentsValue, limit, options.environment));
|
|
36
|
+
const result = await run(command, arguments_(options), timeoutMs);
|
|
36
37
|
const line = result.stdout.trim().split(/\r?\n/u).at(-1) ?? "";
|
|
37
38
|
const parsed = JSON.parse(line);
|
|
38
39
|
if (parsed.schema !== "a1-tabs-maintenance-v1" || typeof parsed.host !== "string")
|
|
@@ -48,19 +49,22 @@ async function firstVerifiedHost(options, roots, platform, architecture) {
|
|
|
48
49
|
? options.environment[PRODUCT_IDENTITY.environment.terminalHostPath]?.trim()
|
|
49
50
|
: undefined;
|
|
50
51
|
if (override) {
|
|
52
|
+
const entry = roots[0] === undefined ? undefined : resolveTerminalHostEntryPath(roots[0]);
|
|
53
|
+
if (entry === undefined)
|
|
54
|
+
return undefined;
|
|
51
55
|
try {
|
|
52
|
-
await access(override, constants.R_OK
|
|
53
|
-
return
|
|
56
|
+
await Promise.all([access(override, constants.R_OK), access(entry, constants.R_OK)]);
|
|
57
|
+
return { executable: process.execPath, entry };
|
|
54
58
|
}
|
|
55
59
|
catch {
|
|
56
60
|
return undefined;
|
|
57
61
|
}
|
|
58
62
|
}
|
|
59
63
|
for (const root of roots) {
|
|
60
|
-
const
|
|
64
|
+
const addon = resolveTerminalHostPath(root, {}, platform, architecture);
|
|
61
65
|
try {
|
|
62
|
-
await (options.verify ?? verifyTerminalHostArtifact)(
|
|
63
|
-
return
|
|
66
|
+
await (options.verify ?? verifyTerminalHostArtifact)(addon, platform, architecture);
|
|
67
|
+
return { executable: process.execPath, entry: resolveTerminalHostEntryPath(root) };
|
|
64
68
|
}
|
|
65
69
|
catch {
|
|
66
70
|
// Rationale: an older retained release may predate the packaged host; try the next one.
|
|
@@ -68,11 +72,22 @@ async function firstVerifiedHost(options, roots, platform, architecture) {
|
|
|
68
72
|
}
|
|
69
73
|
return undefined;
|
|
70
74
|
}
|
|
75
|
+
function installedEnvironment(environment) {
|
|
76
|
+
const sanitized = { ...environment };
|
|
77
|
+
delete sanitized[PRODUCT_IDENTITY.environment.developmentInstanceId];
|
|
78
|
+
delete sanitized[PRODUCT_IDENTITY.environment.terminalHostPath];
|
|
79
|
+
return sanitized;
|
|
80
|
+
}
|
|
71
81
|
function unavailable(action, message) {
|
|
72
82
|
return { schema: "a1-tabs-maintenance-v1", command: action, host: "unavailable", message, exitCode: 3 };
|
|
73
83
|
}
|
|
74
84
|
/** The largest maintenance report accepted from the terminal host. */
|
|
75
85
|
export const MAX_MAINTENANCE_REPORT_BYTES = 4 * 1024 * 1024;
|
|
76
|
-
async function runHost(
|
|
77
|
-
return await runBoundedHostCommand(
|
|
86
|
+
async function runHost(command, arguments_, timeoutMs, environment) {
|
|
87
|
+
return await runBoundedHostCommand(command, arguments_, {
|
|
88
|
+
environment,
|
|
89
|
+
maxStdoutBytes: MAX_MAINTENANCE_REPORT_BYTES,
|
|
90
|
+
timeoutMs,
|
|
91
|
+
label: "terminal host maintenance",
|
|
92
|
+
});
|
|
78
93
|
}
|
|
@@ -8,7 +8,6 @@ export interface SubmittedPromptRecord {
|
|
|
8
8
|
readonly fileExisted: boolean;
|
|
9
9
|
readonly kind: "prompt" | "steer" | "follow-up";
|
|
10
10
|
readonly text: string;
|
|
11
|
-
readonly images?: readonly unknown[];
|
|
12
11
|
readonly at: string;
|
|
13
12
|
}
|
|
14
13
|
export interface CorrelatedPromptRecord {
|
|
@@ -33,7 +32,6 @@ export interface PromptSubmission {
|
|
|
33
32
|
readonly fileExisted: boolean;
|
|
34
33
|
readonly kind: SubmittedPromptRecord["kind"];
|
|
35
34
|
readonly text: string;
|
|
36
|
-
readonly images?: readonly unknown[];
|
|
37
35
|
}
|
|
38
36
|
export interface SessionJournalEntry {
|
|
39
37
|
readonly entryId: string;
|
|
@@ -30,7 +30,6 @@ export class PromptJournal {
|
|
|
30
30
|
type: "submitted",
|
|
31
31
|
id: randomUUID(),
|
|
32
32
|
...submission,
|
|
33
|
-
...(submission.images === undefined ? {} : { images: submission.images }),
|
|
34
33
|
at: new Date().toISOString(),
|
|
35
34
|
};
|
|
36
35
|
await this.#append(record, "journal-before-append", "journal-after-append", "journal-after-sync");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type TerminalHostCommand } from "./ensure-terminal-host.js";
|
|
1
2
|
export interface ResidentReleaseHoldReport {
|
|
2
3
|
/** False when any resident record or identity could not be verified; callers then keep every release. */
|
|
3
4
|
readonly complete: boolean;
|
|
@@ -11,8 +12,8 @@ export interface ResidentReleaseHoldOptions {
|
|
|
11
12
|
readonly platform?: NodeJS.Platform;
|
|
12
13
|
readonly architecture?: string;
|
|
13
14
|
readonly timeoutMs?: number;
|
|
14
|
-
readonly verify?: (
|
|
15
|
-
readonly run?: (
|
|
15
|
+
readonly verify?: (addon: string, platform: NodeJS.Platform, architecture: string) => Promise<unknown>;
|
|
16
|
+
readonly run?: (command: TerminalHostCommand, arguments_: readonly string[], timeoutMs: number) => Promise<{
|
|
16
17
|
readonly code: number;
|
|
17
18
|
readonly stdout: string;
|
|
18
19
|
}>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readdir, stat } from "node:fs/promises";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
+
import { PRODUCT_IDENTITY } from "../../product-identity.js";
|
|
3
4
|
import { runBoundedHostCommand } from "./bounded-host-command.js";
|
|
4
|
-
import { resolveTerminalHostPath } from "./ensure-terminal-host.js";
|
|
5
|
+
import { resolveTerminalHostEntryPath, resolveTerminalHostPath } from "./ensure-terminal-host.js";
|
|
5
6
|
import { verifyTerminalHostArtifact } from "./terminal-host-artifact.js";
|
|
6
7
|
const COMPLETE_EMPTY = Object.freeze({ complete: true, releaseRoots: Object.freeze([]), uncertain: Object.freeze([]) });
|
|
7
8
|
/**
|
|
@@ -15,22 +16,22 @@ export async function readResidentReleaseHolds(options) {
|
|
|
15
16
|
const platform = options.platform ?? process.platform;
|
|
16
17
|
const architecture = options.architecture ?? process.arch;
|
|
17
18
|
const verify = options.verify ?? verifyTerminalHostArtifact;
|
|
18
|
-
let
|
|
19
|
+
let command;
|
|
19
20
|
for (const root of options.releaseRoots) {
|
|
20
21
|
const candidate = resolveTerminalHostPath(root, {}, platform, architecture);
|
|
21
22
|
try {
|
|
22
23
|
await verify(candidate, platform, architecture);
|
|
23
|
-
|
|
24
|
+
command = { executable: process.execPath, entry: resolveTerminalHostEntryPath(root) };
|
|
24
25
|
break;
|
|
25
26
|
}
|
|
26
27
|
catch {
|
|
27
28
|
// Rationale: an older release may predate the packaged host; another retained release may still carry it.
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
|
-
if (!
|
|
31
|
+
if (!command)
|
|
31
32
|
return incomplete("no verified terminal host is installed to inspect resident records");
|
|
32
33
|
try {
|
|
33
|
-
const result = await (options.run ?? runHost)(
|
|
34
|
+
const result = await (options.run ?? runHost)(command, ["release-holds", "--data-dir", options.dataDir], options.timeoutMs ?? 5_000);
|
|
34
35
|
const report = JSON.parse(result.stdout.trim().split(/\r?\n/u).at(-1) ?? "");
|
|
35
36
|
if (result.code !== 0 || report.schema !== "a1-resident-release-holds-v1" || typeof report.complete !== "boolean"
|
|
36
37
|
|| !isStringArray(report.releaseRoots) || !isStringArray(report.uncertain)) {
|
|
@@ -72,6 +73,14 @@ function isStringArray(value) {
|
|
|
72
73
|
}
|
|
73
74
|
/** The largest release-hold report accepted from the terminal host. */
|
|
74
75
|
export const MAX_RELEASE_HOLD_REPORT_BYTES = 1024 * 1024;
|
|
75
|
-
async function runHost(
|
|
76
|
-
|
|
76
|
+
async function runHost(command, arguments_, timeoutMs) {
|
|
77
|
+
const environment = { ...process.env };
|
|
78
|
+
delete environment[PRODUCT_IDENTITY.environment.developmentInstanceId];
|
|
79
|
+
delete environment[PRODUCT_IDENTITY.environment.terminalHostPath];
|
|
80
|
+
return await runBoundedHostCommand(command, arguments_, {
|
|
81
|
+
environment,
|
|
82
|
+
maxStdoutBytes: MAX_RELEASE_HOLD_REPORT_BYTES,
|
|
83
|
+
timeoutMs,
|
|
84
|
+
label: "terminal host release-hold inspection",
|
|
85
|
+
});
|
|
77
86
|
}
|
|
@@ -3,7 +3,6 @@ export declare const RESIDENT_TABS_MAX_CONCURRENT_STARTS_CAP = 8;
|
|
|
3
3
|
export declare const RESIDENT_TABS_PREWARM_COUNT = 1;
|
|
4
4
|
export declare const RESIDENT_TABS_PREWARM_CAP = 1;
|
|
5
5
|
export interface ResidentTabLaunchSettings {
|
|
6
|
-
readonly tabsMax: number;
|
|
7
6
|
readonly tabsAutoName: boolean;
|
|
8
7
|
readonly maxConcurrentStarts: number;
|
|
9
8
|
readonly prewarmCount: number;
|
|
@@ -7,7 +7,6 @@ export const RESIDENT_TABS_MAX_CONCURRENT_STARTS_CAP = 8;
|
|
|
7
7
|
export const RESIDENT_TABS_PREWARM_COUNT = 1;
|
|
8
8
|
export const RESIDENT_TABS_PREWARM_CAP = 1;
|
|
9
9
|
const DEFAULTS = Object.freeze({
|
|
10
|
-
tabsMax: 10,
|
|
11
10
|
tabsAutoName: true,
|
|
12
11
|
maxConcurrentStarts: RESIDENT_TABS_MAX_CONCURRENT_STARTS,
|
|
13
12
|
prewarmCount: RESIDENT_TABS_PREWARM_COUNT,
|
|
@@ -46,17 +45,12 @@ export async function readResidentTabLaunchSettings(profileDir, namespace = "a1"
|
|
|
46
45
|
}
|
|
47
46
|
function resolveLaunchValues(document, migrations) {
|
|
48
47
|
const resolution = resolveOwnedUiSettings({
|
|
49
|
-
declarations: [
|
|
50
|
-
OWNED_SETTING_DECLARATIONS.tabsMax,
|
|
51
|
-
OWNED_SETTING_DECLARATIONS.tabsAutoName,
|
|
52
|
-
],
|
|
48
|
+
declarations: [OWNED_SETTING_DECLARATIONS.tabsAutoName],
|
|
53
49
|
migrations,
|
|
54
50
|
document,
|
|
55
51
|
currentVersion: OWNED_UI_SETTINGS_VERSION,
|
|
56
52
|
});
|
|
57
|
-
const tabsMax = document.values?.["tabsMax"] ?? settingValue(resolution, "tabsMax");
|
|
58
53
|
return {
|
|
59
|
-
tabsMax: typeof tabsMax === "number" && Number.isFinite(tabsMax) ? Math.max(1, Math.min(50, Math.trunc(tabsMax))) : DEFAULTS.tabsMax,
|
|
60
54
|
tabsAutoName: settingValue(resolution, "tabsAutoName") === true,
|
|
61
55
|
maxConcurrentStarts: RESIDENT_TABS_MAX_CONCURRENT_STARTS,
|
|
62
56
|
prewarmCount: RESIDENT_TABS_PREWARM_COUNT,
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
export declare const TERMINAL_HOST_MANIFEST_FILENAME = "terminal-host.manifest.json";
|
|
2
|
-
export
|
|
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
|
-
}
|
|
2
|
+
export type TerminalHostArtifactErrorCode = "TERMINAL_HOST_ARTIFACT_MISSING" | "TERMINAL_HOST_ARTIFACT_TAMPERED" | "TERMINAL_HOST_ARTIFACT_INCOMPATIBLE" | "TERMINAL_HOST_ARTIFACT_UNSUPPORTED";
|
|
9
3
|
export declare class TerminalHostArtifactError extends Error {
|
|
10
|
-
readonly code:
|
|
11
|
-
|
|
4
|
+
readonly code: TerminalHostArtifactErrorCode;
|
|
5
|
+
readonly manifestPath: string;
|
|
6
|
+
constructor(code: TerminalHostArtifactErrorCode, manifestPath: string, message: string);
|
|
7
|
+
}
|
|
8
|
+
export interface VerifiedTerminalHostArtifact {
|
|
9
|
+
readonly addon: string;
|
|
10
|
+
readonly entry: string;
|
|
11
|
+
readonly generation: 1;
|
|
12
12
|
}
|
|
13
|
-
/**
|
|
14
|
-
|
|
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>;
|
|
13
|
+
/** Verifies every runtime file certified by the terminal-host v2 manifest before Node loads it. */
|
|
14
|
+
export declare function verifyTerminalHostArtifact(addonPath: string, platform?: NodeJS.Platform, architecture?: string): Promise<VerifiedTerminalHostArtifact>;
|