@timurproko/a1 0.2.7-dev.792 → 0.2.7-dev.801
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/session-shell/clipboard-diagnostics.js +1 -1
- package/dist/app/session-shell/response-copy-helper.js +21 -6
- package/dist/app/session-shell/response-copy-protocol.d.ts +1 -1
- package/dist/app/session-shell/response-copy-transport.d.ts +3 -6
- package/dist/app/session-shell/response-copy-transport.js +60 -17
- package/dist/app/session-shell/session-shell-root.d.ts +2 -0
- package/dist/app/session-shell/session-shell-root.js +6 -1
- package/dist/app/session-shell/session-shell.d.ts +1 -0
- package/dist/app/session-shell/session-shell.js +22 -19
- package/dist/app/session-shell/shell-host.js +11 -0
- package/dist/cli/tabs.js +1 -1
- package/dist/composition/owned-ui.d.ts +1 -0
- package/dist/composition/owned-ui.js +16 -3
- package/dist/features/resident-tab/shortcut-declarations.d.ts +1 -1
- package/dist/features/resident-tab/shortcut-declarations.js +1 -0
- package/dist/foundation/launch-guardian/exit-watcher.d.ts +14 -0
- package/dist/foundation/launch-guardian/exit-watcher.js +94 -0
- package/dist/foundation/launch-guardian/main.d.ts +2 -1
- package/dist/foundation/launch-guardian/main.js +58 -6
- package/dist/foundation/process-containment/{native-guardian-containment.d.ts → addon-containment.d.ts} +7 -5
- package/dist/foundation/process-containment/addon-containment.js +139 -0
- package/dist/foundation/process-containment/addon-process-inspector.d.ts +11 -0
- package/dist/foundation/process-containment/addon-process-inspector.js +36 -0
- package/dist/foundation/process-containment/addon.d.ts +42 -0
- package/dist/foundation/process-containment/addon.js +57 -0
- package/dist/foundation/process-containment/artifact.d.ts +1 -1
- package/dist/foundation/process-containment/artifact.js +6 -5
- package/dist/foundation/process-containment/contracts.d.ts +2 -0
- package/dist/foundation/process-containment/index.d.ts +4 -4
- package/dist/foundation/process-containment/index.js +3 -3
- package/dist/foundation/process-containment/native-process-inspector.d.ts +2 -1
- package/dist/foundation/process-containment/native-process-inspector.js +10 -10
- package/dist/foundation/release/bootstrap.js +53 -16
- package/dist/foundation/release/index.d.ts +1 -1
- package/dist/foundation/release/index.js +1 -1
- package/dist/foundation/release/release-store.d.ts +12 -0
- package/dist/foundation/release/release-store.js +124 -2
- package/dist/foundation/resident-tabs/bounded-host-command.d.ts +3 -1
- package/dist/foundation/resident-tabs/bounded-host-command.js +6 -2
- package/dist/foundation/resident-tabs/bridge-client.d.ts +2 -0
- package/dist/foundation/resident-tabs/bridge-client.js +13 -0
- package/dist/foundation/resident-tabs/doctor.d.ts +0 -1
- package/dist/foundation/resident-tabs/doctor.js +4 -4
- package/dist/foundation/resident-tabs/ensure-terminal-host.d.ts +7 -2
- package/dist/foundation/resident-tabs/ensure-terminal-host.js +15 -11
- package/dist/foundation/resident-tabs/fixture-control.d.ts +7 -0
- package/dist/foundation/resident-tabs/fixture-control.js +35 -0
- 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/progress-observation.d.ts +7 -0
- package/dist/foundation/resident-tabs/progress-observation.js +119 -0
- package/dist/foundation/resident-tabs/release-holds.d.ts +3 -2
- package/dist/foundation/resident-tabs/release-holds.js +16 -7
- package/dist/foundation/resident-tabs/settings.d.ts +0 -1
- package/dist/foundation/resident-tabs/settings.js +1 -7
- package/dist/foundation/resident-tabs/terminal-host-artifact.d.ts +11 -15
- package/dist/foundation/resident-tabs/terminal-host-artifact.js +57 -32
- package/dist/foundation/session-writer-lock/index.d.ts +1 -3
- package/dist/foundation/session-writer-lock/index.js +8 -6
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +2 -1
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +2 -0
- package/dist/integrations/pi/engine/session-runtime.js +5 -2
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +11 -0
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +30 -1
- package/dist/native/darwin-arm64/manifest.json +8 -6
- package/dist/native/darwin-arm64/process-guardian.node +0 -0
- package/dist/native/darwin-arm64/terminal-host-THIRD-PARTY-NOTICES.txt +1 -1
- package/dist/native/darwin-arm64/terminal-host.manifest.json +14 -13
- package/dist/native/darwin-arm64/terminal-host.node +0 -0
- package/dist/native/darwin-x64/manifest.json +9 -7
- package/dist/native/darwin-x64/process-guardian.node +0 -0
- package/dist/native/darwin-x64/terminal-host-THIRD-PARTY-NOTICES.txt +1 -1
- package/dist/native/darwin-x64/terminal-host.manifest.json +15 -14
- package/dist/native/darwin-x64/terminal-host.node +0 -0
- package/dist/native/linux-x64/manifest.json +8 -6
- package/dist/native/linux-x64/process-guardian.node +0 -0
- package/dist/native/linux-x64/terminal-host-THIRD-PARTY-NOTICES.txt +1 -1
- package/dist/native/linux-x64/terminal-host.manifest.json +14 -13
- package/dist/native/linux-x64/terminal-host.node +0 -0
- package/dist/native/win32-x64/manifest.json +8 -6
- package/dist/native/win32-x64/process-guardian.node +0 -0
- package/dist/native/win32-x64/terminal-host-THIRD-PARTY-NOTICES.txt +1 -1
- package/dist/native/win32-x64/terminal-host.manifest.json +14 -13
- package/dist/native/win32-x64/terminal-host.node +0 -0
- package/dist/product-identity.d.ts +3 -3
- package/dist/product-identity.js +3 -3
- package/dist/product-identity.json +9 -2
- package/dist/runtime-payload-inventory.json +1 -0
- package/dist/ui/settings/declarations.d.ts +0 -12
- package/dist/ui/settings/declarations.js +0 -10
- package/docs/architecture/boundaries.md +10 -9
- package/docs/architecture/process-guardian-provenance.md +17 -11
- package/docs/architecture/resident-tabs.md +46 -40
- package/docs/architecture/terminal-host-provenance.md +5 -3
- package/docs/architecture/toolchain.md +8 -6
- package/docs/ci-release-runbook.md +1 -1
- package/docs/openspec-archive-automation.md +23 -9
- package/docs/validation.md +6 -2
- package/package.json +6 -3
- package/dist/foundation/process-containment/darwin-process-inspector.d.ts +0 -5
- package/dist/foundation/process-containment/darwin-process-inspector.js +0 -7
- package/dist/foundation/process-containment/native-guardian-containment.js +0 -152
- package/dist/foundation/process-containment/windows-process-inspector.d.ts +0 -19
- package/dist/foundation/process-containment/windows-process-inspector.js +0 -56
- package/dist/native/darwin-arm64/a1_session_lock.node +0 -0
- package/dist/native/darwin-arm64/process-guardian +0 -0
- package/dist/native/darwin-arm64/terminal-host +0 -0
- package/dist/native/darwin-x64/a1_session_lock.node +0 -0
- package/dist/native/darwin-x64/process-guardian +0 -0
- package/dist/native/darwin-x64/terminal-host +0 -0
- package/dist/native/linux-x64/a1_session_lock.node +0 -0
- package/dist/native/linux-x64/process-guardian +0 -0
- package/dist/native/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
|
@@ -39,7 +39,7 @@ export class ClipboardDiagnosticCapture {
|
|
|
39
39
|
// Security: project only declared scalar fields. Never serialize the supplied event object or raw errors.
|
|
40
40
|
this.#records.push({ source, phase: event.phase, atMs: scalar(event.atMs), request: event.request !== undefined && event.request < 0 ? -scalar(-event.request) : scalar(event.request), pending: scalar(event.pending), bytes: scalar(event.bytes), elapsedMs: scalar(event.elapsedMs),
|
|
41
41
|
sourceUnits: scalar(event.sourceUnits), pendingBytes,
|
|
42
|
-
transport: ["native", "terminal", "provided", "injected"].includes(event.transport ?? "") ? event.transport : "none",
|
|
42
|
+
transport: ["native", "terminal", "platform", "provided", "injected"].includes(event.transport ?? "") ? event.transport : "none",
|
|
43
43
|
outcome: ["ready", "failed", "canceled", "delivered", "submitted-unverified", "timed-out", "superseded"].includes(event.outcome ?? "") ? event.outcome : "none" });
|
|
44
44
|
if (this.#records.length > 128)
|
|
45
45
|
this.#records.shift();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MAX_COPY_ROWS, MAX_COPY_SOURCE_UNITS, selectionCopyRowText } from "../../ui/components/index.js";
|
|
2
2
|
import { COPY_CHUNK_UNITS, MAX_COPY_BYTES, MAX_COPY_CONTROL_BYTES } from "./response-copy-protocol.js";
|
|
3
|
+
const NATIVE_RETRY_DELAYS_MS = [20, 60];
|
|
3
4
|
// Security: this isolated executor never inherits the UI terminal and never logs clipboard contents.
|
|
4
5
|
let header;
|
|
5
6
|
let fragments = [];
|
|
@@ -24,6 +25,14 @@ function nextOutput() {
|
|
|
24
25
|
send({ kind: "data", text });
|
|
25
26
|
}
|
|
26
27
|
function send(message) { process.send?.(message); }
|
|
28
|
+
function transientClipboardError(error) {
|
|
29
|
+
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
30
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
31
|
+
return /^(?:EAGAIN|EBUSY)$/iu.test(code) || /(?:busy|locked|contention|temporar|try again)/iu.test(message);
|
|
32
|
+
}
|
|
33
|
+
function delay(milliseconds) {
|
|
34
|
+
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
|
35
|
+
}
|
|
27
36
|
function finish(result, control) {
|
|
28
37
|
if (finished)
|
|
29
38
|
return;
|
|
@@ -97,13 +106,19 @@ async function receive(message) {
|
|
|
97
106
|
return;
|
|
98
107
|
}
|
|
99
108
|
send({ kind: "phase", phase: "submitting", bytes });
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
109
|
+
for (let attempt = 0; attempt <= NATIVE_RETRY_DELAYS_MS.length; attempt++) {
|
|
110
|
+
try {
|
|
111
|
+
await native.setText(text);
|
|
112
|
+
finish({ outcome: "delivered" });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (!transientClipboardError(error) || attempt === NATIVE_RETRY_DELAYS_MS.length)
|
|
117
|
+
break;
|
|
118
|
+
await delay(NATIVE_RETRY_DELAYS_MS[attempt]);
|
|
119
|
+
}
|
|
106
120
|
}
|
|
121
|
+
finish({ outcome: "failed", failure: "denied" });
|
|
107
122
|
return;
|
|
108
123
|
}
|
|
109
124
|
else
|
|
@@ -19,7 +19,7 @@ export interface ResponseCopyEvent {
|
|
|
19
19
|
readonly pending: number;
|
|
20
20
|
readonly sourceUnits: number;
|
|
21
21
|
readonly bytes?: number;
|
|
22
|
-
readonly transport?: "native" | "terminal" | "injected";
|
|
22
|
+
readonly transport?: "native" | "terminal" | "platform" | "injected";
|
|
23
23
|
readonly outcome?: CopyOutcome;
|
|
24
24
|
}
|
|
25
25
|
/** One request per helper lifetime. Each source fragment is acknowledged before the next is sent. */
|
|
@@ -7,7 +7,7 @@ export interface ResponseCopyJob {
|
|
|
7
7
|
readonly stopped: Promise<void>;
|
|
8
8
|
cancel(): void;
|
|
9
9
|
}
|
|
10
|
-
export type CopyPhaseObserver = (phase: "extracted" | "encoded" | "submitting", bytes: number, transport: "native" | "terminal" | "injected") => void;
|
|
10
|
+
export type CopyPhaseObserver = (phase: "extracted" | "encoded" | "submitting", bytes: number, transport: "native" | "terminal" | "platform" | "injected") => void;
|
|
11
11
|
export type ResponseCopyExecutor = (snapshot: SelectionCopySnapshot, phase: CopyPhaseObserver) => ResponseCopyJob;
|
|
12
12
|
/** The production executor also holds a spare helper; the owner warms it at start and disposes it at shutdown unless the pool is shared. */
|
|
13
13
|
export interface OwnedResponseCopyExecutor extends ResponseCopyExecutor {
|
|
@@ -22,17 +22,14 @@ export interface ResponseCopyTerminal {
|
|
|
22
22
|
}
|
|
23
23
|
/** Never substitute a remote host's native clipboard for the terminal user's clipboard. */
|
|
24
24
|
export declare function responseCopyDestination(env: NodeJS.ProcessEnv): "native" | "terminal";
|
|
25
|
-
/** Node documents synchronous POSIX TTY and Windows pipe writes; do not put clipboard traffic there. */
|
|
26
|
-
export declare function hasAsyncClipboardOutput(platform?: NodeJS.Platform, stdout?: {
|
|
27
|
-
readonly isTTY?: boolean | undefined;
|
|
28
|
-
readonly fd?: number | undefined;
|
|
29
|
-
}): boolean;
|
|
30
25
|
/** Isolates preparation and native clipboard access; only the owner may submit terminal controls. */
|
|
31
26
|
export declare function createResponseCopyExecutor(options?: {
|
|
32
27
|
readonly terminal?: ResponseCopyTerminal;
|
|
33
28
|
readonly destination?: "native" | "terminal";
|
|
34
29
|
/** Explicit host seam: must be non-blocking and honor cancellation, or its unresolved lifetime is quarantined. */
|
|
35
30
|
readonly writeText?: (text: string, signal: AbortSignal) => Promise<void>;
|
|
31
|
+
/** Platform/fault-test seam; production writes through the supported operating-system command. */
|
|
32
|
+
readonly platformWriteText?: (text: string, signal: AbortSignal) => Promise<boolean>;
|
|
36
33
|
/** Package/fault-test seam: the production helper is always an emitted sibling. */
|
|
37
34
|
readonly helper?: URL;
|
|
38
35
|
/** Idle bound for the spare helper; production keeps the default. */
|
|
@@ -1,25 +1,58 @@
|
|
|
1
|
-
import { fork } from "node:child_process";
|
|
2
|
-
import { fstatSync } from "node:fs";
|
|
1
|
+
import { execFile, fork } from "node:child_process";
|
|
3
2
|
import { HelperPool } from "./helper-pool.js";
|
|
3
|
+
import { PRODUCT_IDENTITY } from "../../product-identity.js";
|
|
4
4
|
import { COPY_CHUNK_UNITS, COPY_CLEANUP_MS, MAX_COPY_BYTES, MAX_COPY_CONTROL_BYTES } from "./response-copy-protocol.js";
|
|
5
|
+
const PLATFORM_WRITE_TIMEOUT_MS = 1_500;
|
|
5
6
|
/** Never substitute a remote host's native clipboard for the terminal user's clipboard. */
|
|
6
7
|
export function responseCopyDestination(env) {
|
|
7
|
-
return env.SSH_CONNECTION || env.SSH_CLIENT || env.SSH_TTY || env.TMUX || env.STY || env.WSL_DISTRO_NAME
|
|
8
|
+
return env[PRODUCT_IDENTITY.environment.tabId] || env.SSH_CONNECTION || env.SSH_CLIENT || env.SSH_TTY || env.TMUX || env.STY || env.WSL_DISTRO_NAME
|
|
8
9
|
? "terminal" : "native";
|
|
9
10
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
function platformClipboardCommands() {
|
|
12
|
+
if (process.platform === "win32")
|
|
13
|
+
return [{
|
|
14
|
+
command: "powershell.exe",
|
|
15
|
+
args: ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", "$value=[Console]::In.ReadToEnd(); Set-Clipboard -Value $value"],
|
|
16
|
+
}];
|
|
17
|
+
if (process.platform === "darwin")
|
|
18
|
+
return [{ command: "pbcopy", args: [] }];
|
|
19
|
+
return [
|
|
20
|
+
{ command: "wl-copy", args: ["--type", "text/plain;charset=utf-8"] },
|
|
21
|
+
{ command: "xclip", args: ["-selection", "clipboard", "-in"] },
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
async function writePlatformClipboardText(text, signal) {
|
|
25
|
+
for (const route of platformClipboardCommands()) {
|
|
26
|
+
if (signal.aborted)
|
|
27
|
+
return false;
|
|
28
|
+
const delivered = await new Promise(resolve => {
|
|
29
|
+
let settled = false;
|
|
30
|
+
const done = (value) => {
|
|
31
|
+
if (settled)
|
|
32
|
+
return;
|
|
33
|
+
settled = true;
|
|
34
|
+
resolve(value);
|
|
35
|
+
};
|
|
36
|
+
const child = execFile(route.command, [...route.args], {
|
|
37
|
+
signal,
|
|
38
|
+
windowsHide: true,
|
|
39
|
+
timeout: PLATFORM_WRITE_TIMEOUT_MS,
|
|
40
|
+
killSignal: "SIGKILL",
|
|
41
|
+
maxBuffer: 1_024,
|
|
42
|
+
}, error => done(error === null));
|
|
43
|
+
child.once("error", () => done(false));
|
|
44
|
+
if (child.stdin === null) {
|
|
45
|
+
child.kill("SIGKILL");
|
|
46
|
+
done(false);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
child.stdin.once("error", () => { child.kill("SIGKILL"); done(false); });
|
|
50
|
+
child.stdin.end(text);
|
|
51
|
+
});
|
|
52
|
+
if (delivered)
|
|
53
|
+
return true;
|
|
22
54
|
}
|
|
55
|
+
return false;
|
|
23
56
|
}
|
|
24
57
|
/** Isolates preparation and native clipboard access; only the owner may submit terminal controls. */
|
|
25
58
|
export function createResponseCopyExecutor(options = {}) {
|
|
@@ -43,8 +76,18 @@ export function createResponseCopyExecutor(options = {}) {
|
|
|
43
76
|
};
|
|
44
77
|
let outcome = await execute(destination);
|
|
45
78
|
// Concurrency: helper results settle only after exit; fallback cannot race an older native write.
|
|
46
|
-
if (!controller.signal.aborted && destination === "native" && outcome.result.
|
|
47
|
-
|
|
79
|
+
if (!controller.signal.aborted && destination === "native" && outcome.result.outcome === "failed") {
|
|
80
|
+
const prepared = await execute("prepare");
|
|
81
|
+
if (prepared.result.outcome === "delivered" && prepared.text !== undefined) {
|
|
82
|
+
phase("submitting", prepared.bytes ?? 0, "platform");
|
|
83
|
+
const delivered = await (options.platformWriteText ?? writePlatformClipboardText)(prepared.text, controller.signal);
|
|
84
|
+
outcome = { kind: "result", result: delivered ? { outcome: "delivered" } : { outcome: "failed", failure: "unavailable" } };
|
|
85
|
+
}
|
|
86
|
+
else
|
|
87
|
+
outcome = { kind: "result", result: { outcome: "failed", failure: "transport" } };
|
|
88
|
+
if (!controller.signal.aborted && outcome.result.outcome === "failed" && options.terminal !== undefined) {
|
|
89
|
+
outcome = await execute("terminal");
|
|
90
|
+
}
|
|
48
91
|
}
|
|
49
92
|
if (controller.signal.aborted)
|
|
50
93
|
return { outcome: "canceled" };
|
|
@@ -46,6 +46,7 @@ export interface OwnedUiShellEngineOptions {
|
|
|
46
46
|
/** Resident tab quit routes detach the outer client without stopping this engine session. */
|
|
47
47
|
readonly residentTab?: {
|
|
48
48
|
requestDetach(): Promise<boolean>;
|
|
49
|
+
closeOwnedRoute?(route: string): void;
|
|
49
50
|
reportStatus?(status: "working" | "idle" | "stalled"): void;
|
|
50
51
|
readonly stallNoticeSeconds?: number;
|
|
51
52
|
admit?(input: {
|
|
@@ -191,6 +192,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
|
|
|
191
192
|
readonly requestHyperlinkCleanup?: (rows?: readonly number[]) => void;
|
|
192
193
|
readonly enableDockInputReuse?: boolean;
|
|
193
194
|
readonly persistentHistory?: boolean;
|
|
195
|
+
readonly hideSettingsCommand?: boolean;
|
|
194
196
|
readonly historyEditor?: import("../../integrations/pi/components/index.js").HistoryEditorConstructor;
|
|
195
197
|
readonly onSubmit: (text: string) => void;
|
|
196
198
|
readonly onPasteRejected?: (error: unknown) => void;
|
|
@@ -438,6 +438,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
438
438
|
? Math.max(0, dock.inputRows - 2)
|
|
439
439
|
: 0;
|
|
440
440
|
const copyableDockRows = [
|
|
441
|
+
...dock.copyableMessageRows,
|
|
441
442
|
...Array.from({ length: promptCopyRows }, (_value, index) => ({
|
|
442
443
|
row: editorOffset + index + 1,
|
|
443
444
|
prompt: index === 0 ? "first" : "continuation",
|
|
@@ -620,7 +621,10 @@ export class OwnedUiSessionShellRoot {
|
|
|
620
621
|
#renderDockLayout(width) {
|
|
621
622
|
const queued = this.#customViewport ? [] : this.#renderQueued(width);
|
|
622
623
|
const statusRows = this.#customViewport ? this.#status.renderDock(width) : this.#renderStatus(width);
|
|
623
|
-
const
|
|
624
|
+
const noticeRows = this.#renderDockNotice(width);
|
|
625
|
+
const transientRows = [...queued, ...statusRows, ...noticeRows];
|
|
626
|
+
const messageStart = queued.length;
|
|
627
|
+
const copyableMessageRows = Array.from({ length: statusRows.length + noticeRows.length }, (_value, index) => ({ row: messageStart + index }));
|
|
624
628
|
const aboveWidgets = this.#renderWidgets("aboveEditor", width);
|
|
625
629
|
const input = this.#inputSurface.render(width);
|
|
626
630
|
const body = this.usesDefaultInputSurface() ? this.editor.bodyGeometry?.() : undefined;
|
|
@@ -634,6 +638,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
634
638
|
editorOffset: inputOffset + (body?.rowOffset ?? 0),
|
|
635
639
|
inputRows: body?.rowCount ?? input.length,
|
|
636
640
|
footerRows: footer.length,
|
|
641
|
+
copyableMessageRows,
|
|
637
642
|
};
|
|
638
643
|
}
|
|
639
644
|
#renderCopyAcknowledgement(width) {
|
|
@@ -72,6 +72,7 @@ export declare class OwnedUiSessionPresenter implements OwnedUiHostedPresenter {
|
|
|
72
72
|
readonly mode: "regular" | "fullscreen";
|
|
73
73
|
readonly columns: number;
|
|
74
74
|
}): OwnedUiPresenterRelease;
|
|
75
|
+
openOwnedRoute(route: string): void;
|
|
75
76
|
hotkeysPresentation(): PiShellHotkeysPresentation;
|
|
76
77
|
}
|
|
77
78
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ResponseCopyCoordinator } from "./response-copy-coordinator.js";
|
|
2
|
-
import { createResponseCopyExecutor,
|
|
2
|
+
import { createResponseCopyExecutor, responseCopyDestination } from "./response-copy-transport.js";
|
|
3
3
|
import { MAX_COPY_CONTROL_BYTES } from "./response-copy-protocol.js";
|
|
4
4
|
import { PromptHistoryController } from "./prompt-history-controller.js";
|
|
5
5
|
import { PRODUCT_TEXT } from "../../product-identity.js";
|
|
@@ -129,31 +129,24 @@ export class OwnedUiSessionPresenter {
|
|
|
129
129
|
// throw releases them in reverse, so a shell that never finished constructing holds nothing.
|
|
130
130
|
const constructed = [() => host.disconnect(this)];
|
|
131
131
|
try {
|
|
132
|
-
const terminalCopy = terminal !== undefined || hasAsyncClipboardOutput();
|
|
133
132
|
this.#responseCopy = this.#customViewport ? new ResponseCopyCoordinator({
|
|
134
133
|
execute: responseCopy?.execute ?? (this.#copyExecutor = createResponseCopyExecutor({
|
|
135
134
|
...(sharedClipboard === undefined ? {} : { pool: sharedClipboard.copy }),
|
|
136
135
|
...(terminal === undefined && clipboard === undefined ? {} : { destination: "terminal" }),
|
|
137
136
|
...(clipboard?.writeText === undefined ? {} : { writeText: (text, signal) => clipboard.writeText(text, signal) }),
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
137
|
+
// Invariant: the owned host is the route contract. Resident and pseudoterminal children must not infer
|
|
138
|
+
// clipboard reachability from Node's view of stdout, and inactive presenters are fenced here.
|
|
139
|
+
terminal: { submit: async (control, signal) => {
|
|
140
|
+
if (signal.aborted || this.#disposed || !this.#terminal.running || !this.#terminal.attached)
|
|
141
|
+
throw new Error("Copy canceled");
|
|
142
|
+
// Performance: never grow the real terminal's pending buffer with another clipboard payload.
|
|
143
|
+
if (terminal === undefined && process.stdout.writableLength + control.length > MAX_COPY_CONTROL_BYTES) {
|
|
144
|
+
throw new Error("Clipboard terminal is busy");
|
|
145
|
+
}
|
|
146
|
+
this.#terminal.writeControl(control);
|
|
147
|
+
} },
|
|
147
148
|
})),
|
|
148
149
|
...(responseCopy?.onEvent === undefined ? {} : { onEvent: responseCopy.onEvent }),
|
|
149
|
-
onFailure: result => {
|
|
150
|
-
if (this.#disposed || !this.#terminal.running)
|
|
151
|
-
return;
|
|
152
|
-
this.root.appendWorkflowStatus(result.outcome === "timed-out" ? "Copy timed out; the clipboard did not respond."
|
|
153
|
-
: result.failure === "size" ? "Selection exceeds this clipboard route's size limit."
|
|
154
|
-
: "Could not copy the selection; the clipboard is unavailable.");
|
|
155
|
-
this.#terminal.requestRender();
|
|
156
|
-
},
|
|
157
150
|
}) : null;
|
|
158
151
|
constructed.push(() => { this.#responseCopy?.dispose(); this.#copyExecutor?.dispose(); });
|
|
159
152
|
const readClipboard = async (signal) => {
|
|
@@ -189,6 +182,7 @@ export class OwnedUiSessionPresenter {
|
|
|
189
182
|
}),
|
|
190
183
|
enableDockInputReuse: inputPresentation?.viewportReuse !== false,
|
|
191
184
|
footerSessionName: this.#residentTabOptions === undefined,
|
|
185
|
+
hideSettingsCommand: this.#residentTabOptions !== undefined,
|
|
192
186
|
persistentHistory: this.#customViewport && promptHistory !== undefined,
|
|
193
187
|
...(promptHistory === undefined ? {} : { historyEditor: promptHistory.editor }),
|
|
194
188
|
onSubmit: text => { void this.submit(text).catch(() => this.#reportSubmissionError()); },
|
|
@@ -1343,6 +1337,8 @@ export class OwnedUiSessionPresenter {
|
|
|
1343
1337
|
return { outcome: "completed", diagnostic: null };
|
|
1344
1338
|
}
|
|
1345
1339
|
if (request.command === "settings" && request.selection === undefined && request.confirmed === undefined) {
|
|
1340
|
+
if (this.#residentTabOptions !== undefined)
|
|
1341
|
+
return rejected("Settings is available from the global Settings tab.");
|
|
1346
1342
|
this.showSettingsSelector();
|
|
1347
1343
|
return { outcome: "completed", diagnostic: null };
|
|
1348
1344
|
}
|
|
@@ -1717,6 +1713,7 @@ export class OwnedUiSessionPresenter {
|
|
|
1717
1713
|
void onClosed?.()?.catch(() => undefined);
|
|
1718
1714
|
}
|
|
1719
1715
|
catch { /* Rationale: a failed acknowledgement stays pending. */ }
|
|
1716
|
+
this.#residentTabOptions?.closeOwnedRoute?.(route);
|
|
1720
1717
|
}
|
|
1721
1718
|
},
|
|
1722
1719
|
});
|
|
@@ -1764,6 +1761,9 @@ export class OwnedUiSessionPresenter {
|
|
|
1764
1761
|
});
|
|
1765
1762
|
this.#dialogId = dialog.id;
|
|
1766
1763
|
}
|
|
1764
|
+
openOwnedRoute(route) {
|
|
1765
|
+
this.#openOwnedRoute(route);
|
|
1766
|
+
}
|
|
1767
1767
|
hotkeysPresentation() {
|
|
1768
1768
|
const presentation = this.root.hotkeysPresentation();
|
|
1769
1769
|
return this.#residentTabOptions === undefined ? presentation : { ...presentation, residentTabs: true };
|
|
@@ -1773,6 +1773,9 @@ export class OwnedUiSessionPresenter {
|
|
|
1773
1773
|
const separator = body.search(/\s/);
|
|
1774
1774
|
const name = separator < 0 ? body : body.slice(0, separator);
|
|
1775
1775
|
const argument = separator < 0 ? "" : body.slice(separator + 1).trimStart();
|
|
1776
|
+
if (name === "settings" && this.#residentTabOptions !== undefined) {
|
|
1777
|
+
return rejected("Settings is available from the global Settings tab.");
|
|
1778
|
+
}
|
|
1776
1779
|
if (this.#routeHost?.claims(name))
|
|
1777
1780
|
return this.#openOwnedRoute(name);
|
|
1778
1781
|
if (this.#isWorkflowRoute(name))
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { MOUSE_TRACKING_OFF, MOUSE_TRACKING_ON, parseMouseInput } from "../../ui/components/mouse.js";
|
|
2
2
|
import { readVisibleHyperlinks } from "../../ui/components/visible-hyperlinks.js";
|
|
3
3
|
import { piCanvasBackgroundAnsi } from "../../integrations/pi/components/theme.js";
|
|
4
|
+
import { residentFixtureControl } from "../../foundation/resident-tabs/fixture-control.js";
|
|
5
|
+
import { residentProgressObserver } from "../../foundation/resident-tabs/progress-observation.js";
|
|
4
6
|
import { DamageAwareTerminalAdapter, } from "../../integrations/pi/tui-runtime/damage-aware-terminal.js";
|
|
5
7
|
import { PiTuiRuntimeAdapter } from "../../integrations/pi/tui-runtime/adapter.js";
|
|
6
8
|
import { classifyPiTuiInput } from "../../integrations/pi/tui-runtime/input-presentation-coordinator.js";
|
|
@@ -47,6 +49,8 @@ export class OwnedUiTerminalHost {
|
|
|
47
49
|
this.#customViewport = options.layout === "custom-viewport";
|
|
48
50
|
this.#quitOutro = options.quitOutro;
|
|
49
51
|
const input = options.input;
|
|
52
|
+
const fixtureControl = residentFixtureControl();
|
|
53
|
+
const progressObserver = residentProgressObserver();
|
|
50
54
|
let damageTerminal;
|
|
51
55
|
const surface = {
|
|
52
56
|
render: width => this.#active?.render(width) ?? [],
|
|
@@ -72,6 +76,13 @@ export class OwnedUiTerminalHost {
|
|
|
72
76
|
inspectHyperlinks: readVisibleHyperlinks,
|
|
73
77
|
onResize: () => this.#active?.noteImmediatePresentation(),
|
|
74
78
|
onHyperlinkCleanupRequired: () => this.runtime?.requestRender(true),
|
|
79
|
+
...(fixtureControl === null ? {} : {
|
|
80
|
+
allowWrite: () => !fixtureControl.stopped("child-render-write"),
|
|
81
|
+
writeChunks: data => fixtureControl.fragmentChildWrite(data),
|
|
82
|
+
}),
|
|
83
|
+
...(progressObserver === null ? {} : {
|
|
84
|
+
onWrite: ({ bytes, fragments, transaction, frameId, cause }) => progressObserver.childRenderWrite(bytes, fragments, transaction, frameId, cause),
|
|
85
|
+
}),
|
|
75
86
|
});
|
|
76
87
|
return damageTerminal;
|
|
77
88
|
},
|
package/dist/cli/tabs.js
CHANGED
|
@@ -30,7 +30,7 @@ export async function runTabsCommand(request, options = {}) {
|
|
|
30
30
|
stateDir: report.stateDir,
|
|
31
31
|
hostStatus: report,
|
|
32
32
|
releaseRoots,
|
|
33
|
-
settings: {
|
|
33
|
+
settings: { tabsAutoName: settings.tabsAutoName },
|
|
34
34
|
});
|
|
35
35
|
if (request.json)
|
|
36
36
|
stdout(`${JSON.stringify({ schema: "a1-tabs-doctor-v1", path: bundle.path, bytes: bundle.bytes, dropped: bundle.dropped })}\n`);
|
|
@@ -87,5 +87,6 @@ export declare function composeOwnedUiApplication(options?: OwnedUiCompositionOp
|
|
|
87
87
|
export declare function composeOwnedUi(options?: OwnedUiCompositionOptions): Promise<OwnedUiComposition>;
|
|
88
88
|
export declare function bindResidentTabBridge(tabBridge: TabBridgeClient, backend: Pick<ComposedSessionBackend, "identity" | "session" | "workflows" | "generateResidentTabName">, recovery?: Pick<ResidentTabRecovery, "settled"> | null, options?: {
|
|
89
89
|
readonly autoName?: boolean;
|
|
90
|
+
readonly openRoute?: (route: "settings") => void;
|
|
90
91
|
}): Promise<() => void>;
|
|
91
92
|
export declare function correlateResidentTabSubmission(recovery: Pick<ResidentTabRecovery, "correlated" | "settled">, backend: Pick<ComposedSessionBackend, "identity" | "session">, submissionId: string, entryId: string): Promise<void>;
|
|
@@ -102,7 +102,7 @@ export async function composeOwnedUi(options = {}) {
|
|
|
102
102
|
namespace: PRODUCT_IDENTITY.commandName,
|
|
103
103
|
legacyConfigDir: productPaths.configDir,
|
|
104
104
|
agentProvider: () => backend.settingsPort(),
|
|
105
|
-
...(options.hideTabSettings ? { hiddenOwnedSettingIds: ["
|
|
105
|
+
...(options.hideTabSettings ? { hiddenOwnedSettingIds: ["tabsAutoName"] } : {}),
|
|
106
106
|
hiddenAgentSettingIds: ["fullscreenWheelScrollLines"],
|
|
107
107
|
agentSettingLabelOverrides: { fullscreenCopyOnSelect: "Copy on select" },
|
|
108
108
|
});
|
|
@@ -167,7 +167,7 @@ export async function composeOwnedUi(options = {}) {
|
|
|
167
167
|
if (tabBridge !== null && settings !== null && ownedSurfaces)
|
|
168
168
|
tabBridge.accent(settings.value("accentColor"));
|
|
169
169
|
let removeControllerBoundaryInput = () => { };
|
|
170
|
-
|
|
170
|
+
let unsubscribeTabBridge = () => { };
|
|
171
171
|
let releaseNotes = null;
|
|
172
172
|
let releaseNotesFailure;
|
|
173
173
|
if (ownedSurfaces) {
|
|
@@ -293,6 +293,10 @@ export async function composeOwnedUi(options = {}) {
|
|
|
293
293
|
...(ownedSurfaces ? { sessionLayout: "custom-viewport" } : {}),
|
|
294
294
|
...(tabBridge === null || tabRecovery === null ? {} : { residentTab: {
|
|
295
295
|
requestDetach: () => tabBridge.requestDetach(),
|
|
296
|
+
closeOwnedRoute: route => {
|
|
297
|
+
if (tabBridge.tabId.startsWith("settings-") && route === "settings")
|
|
298
|
+
void tabBridge.requestClose();
|
|
299
|
+
},
|
|
296
300
|
reportStatus: status => tabBridge.status(status),
|
|
297
301
|
stallNoticeSeconds: 300,
|
|
298
302
|
admit: input => tabRecovery.admit({ ...input, incarnation: options.tab.incarnation }),
|
|
@@ -344,6 +348,12 @@ export async function composeOwnedUi(options = {}) {
|
|
|
344
348
|
if (recoveryDegraded)
|
|
345
349
|
presentRecoveryDegraded(true);
|
|
346
350
|
terminalHost.attach(presenter);
|
|
351
|
+
if (tabBridge !== null) {
|
|
352
|
+
unsubscribeTabBridge = await bindResidentTabBridge(tabBridge, backend, tabRecovery, {
|
|
353
|
+
autoName: settings?.value("tabsAutoName") ?? true,
|
|
354
|
+
openRoute: route => presenter.openOwnedRoute(route),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
347
357
|
}
|
|
348
358
|
catch (error) {
|
|
349
359
|
await createdTerminalHost?.dispose().catch(() => undefined);
|
|
@@ -477,7 +487,10 @@ export async function bindResidentTabBridge(tabBridge, backend, recovery = null,
|
|
|
477
487
|
void backend.workflows.executeWorkflow({ command: "name", argument: command.name }).catch(() => { });
|
|
478
488
|
}
|
|
479
489
|
else if (command.type === "route") {
|
|
480
|
-
|
|
490
|
+
if (options.openRoute !== undefined)
|
|
491
|
+
options.openRoute(command.route);
|
|
492
|
+
else
|
|
493
|
+
void backend.workflows.executeWorkflow({ command: command.route, argument: "" }).catch(() => { });
|
|
481
494
|
}
|
|
482
495
|
else if (command.type === "stop") {
|
|
483
496
|
void backend.session.dispose().catch(() => { });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ResidentTabAction = "tabs.new" | "tabs.close" | "tabs.rename" | "tabs.retry" | "tabs.fresh" | "tabs.next" | "tabs.previous" | "tabs.moveRight" | "tabs.moveLeft" | `tabs.jump${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10}`;
|
|
1
|
+
export type ResidentTabAction = "tabs.new" | "tabs.close" | "tabs.rename" | "tabs.settings" | "tabs.retry" | "tabs.fresh" | "tabs.next" | "tabs.previous" | "tabs.moveRight" | "tabs.moveLeft" | `tabs.jump${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10}`;
|
|
2
2
|
export interface ResidentTabShortcut {
|
|
3
3
|
readonly id: ResidentTabAction;
|
|
4
4
|
readonly defaultKey: string | null;
|
|
@@ -13,6 +13,7 @@ export const RESIDENT_TAB_SHORTCUTS = Object.freeze([
|
|
|
13
13
|
{ id: "tabs.new", defaultKey: "alt+a", scope: "global", description: "New A1 tab" },
|
|
14
14
|
{ id: "tabs.close", defaultKey: "alt+w", scope: "global", description: "Close A1 tab" },
|
|
15
15
|
{ id: "tabs.rename", defaultKey: "f2", scope: "global", description: "Rename A1 tab" },
|
|
16
|
+
{ id: "tabs.settings", defaultKey: "alt+.", scope: "global", description: "Open Settings tab" },
|
|
16
17
|
...jumps,
|
|
17
18
|
{ id: "tabs.next", defaultKey: null, scope: "global", description: "View next A1 tab" },
|
|
18
19
|
{ id: "tabs.previous", defaultKey: null, scope: "global", description: "View previous A1 tab" },
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ProcessGuardianAddon } from "../process-containment/index.js";
|
|
2
|
+
interface ExitWatcherOptions {
|
|
3
|
+
readonly guardianPid: number;
|
|
4
|
+
readonly noticePath: string;
|
|
5
|
+
readonly encodedModes: string;
|
|
6
|
+
readonly addonPath: string;
|
|
7
|
+
readonly runtimePid: number;
|
|
8
|
+
readonly processGroup: number | null;
|
|
9
|
+
readonly foregroundGroup: number | null;
|
|
10
|
+
}
|
|
11
|
+
/** Waits outside launch containment, then performs the cleanup its dead guardian could not. */
|
|
12
|
+
export declare function runExitWatcher(arguments_: readonly string[], platform?: NodeJS.Platform, loadAddon?: (path: string) => ProcessGuardianAddon): Promise<number>;
|
|
13
|
+
export declare function exitWatcherArguments(options: ExitWatcherOptions): string[];
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { loadProcessGuardianAddon } from "../process-containment/index.js";
|
|
4
|
+
/** Waits outside launch containment, then performs the cleanup its dead guardian could not. */
|
|
5
|
+
export async function runExitWatcher(arguments_, platform = process.platform, loadAddon = loadProcessGuardianAddon) {
|
|
6
|
+
const options = parseArguments(arguments_);
|
|
7
|
+
const addon = loadAddon(options.addonPath);
|
|
8
|
+
if (platform === "win32")
|
|
9
|
+
addon.ignoreConsoleControl();
|
|
10
|
+
else {
|
|
11
|
+
process.on("SIGINT", ignoreSignal);
|
|
12
|
+
process.on("SIGTTOU", ignoreSignal);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
await addon.waitForProcessExit(options.guardianPid);
|
|
16
|
+
if (platform === "win32") {
|
|
17
|
+
// Rationale: job termination is asynchronous; keep the runtime's final frame ahead of the notice.
|
|
18
|
+
await Promise.race([
|
|
19
|
+
addon.waitForProcessExit(options.runtimePid),
|
|
20
|
+
delay(2_000),
|
|
21
|
+
]);
|
|
22
|
+
}
|
|
23
|
+
else if (options.processGroup !== null) {
|
|
24
|
+
addon.stopProcessGroup(options.processGroup);
|
|
25
|
+
if (options.foregroundGroup !== null)
|
|
26
|
+
addon.restoreForegroundGroup(options.foregroundGroup);
|
|
27
|
+
}
|
|
28
|
+
addon.deliverExitNotice(options.noticePath, options.encodedModes);
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
if (platform !== "win32") {
|
|
33
|
+
process.off("SIGINT", ignoreSignal);
|
|
34
|
+
process.off("SIGTTOU", ignoreSignal);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function exitWatcherArguments(options) {
|
|
39
|
+
return [
|
|
40
|
+
"--guardian-pid", String(options.guardianPid),
|
|
41
|
+
"--notice", options.noticePath,
|
|
42
|
+
"--modes", options.encodedModes,
|
|
43
|
+
"--addon", options.addonPath,
|
|
44
|
+
"--runtime-pid", String(options.runtimePid),
|
|
45
|
+
...(options.processGroup === null ? [] : ["--process-group", String(options.processGroup)]),
|
|
46
|
+
...(options.foregroundGroup === null ? [] : ["--foreground-group", String(options.foregroundGroup)]),
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
function parseArguments(arguments_) {
|
|
50
|
+
const values = new Map();
|
|
51
|
+
for (let index = 0; index < arguments_.length; index += 2) {
|
|
52
|
+
const name = arguments_[index];
|
|
53
|
+
const value = arguments_[index + 1];
|
|
54
|
+
if (!name?.startsWith("--") || value === undefined || values.has(name))
|
|
55
|
+
throw new TypeError("exit watcher arguments are malformed");
|
|
56
|
+
values.set(name, value);
|
|
57
|
+
}
|
|
58
|
+
const guardianPid = positiveInteger(values.get("--guardian-pid"), "guardian PID");
|
|
59
|
+
const runtimePid = positiveInteger(values.get("--runtime-pid"), "runtime PID");
|
|
60
|
+
const noticePath = boundedValue(values.get("--notice"), "notice path");
|
|
61
|
+
const encodedModes = boundedValue(values.get("--modes"), "terminal modes", 32_768);
|
|
62
|
+
const addonPath = boundedValue(values.get("--addon"), "addon path");
|
|
63
|
+
const processGroup = optionalPositiveInteger(values.get("--process-group"), "process group");
|
|
64
|
+
const foregroundGroup = optionalPositiveInteger(values.get("--foreground-group"), "foreground group");
|
|
65
|
+
const expected = 5 + (processGroup === null ? 0 : 1) + (foregroundGroup === null ? 0 : 1);
|
|
66
|
+
if (values.size !== expected)
|
|
67
|
+
throw new TypeError("exit watcher received an unknown argument");
|
|
68
|
+
return { guardianPid, noticePath, encodedModes, addonPath, runtimePid, processGroup, foregroundGroup };
|
|
69
|
+
}
|
|
70
|
+
function positiveInteger(value, name) {
|
|
71
|
+
const parsed = Number(value);
|
|
72
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0)
|
|
73
|
+
throw new TypeError(`exit watcher ${name} must be a positive safe integer`);
|
|
74
|
+
return parsed;
|
|
75
|
+
}
|
|
76
|
+
function optionalPositiveInteger(value, name) {
|
|
77
|
+
return value === undefined ? null : positiveInteger(value, name);
|
|
78
|
+
}
|
|
79
|
+
function boundedValue(value, name, limit = 4_096) {
|
|
80
|
+
if (!value || value.length > limit || value.includes("\0"))
|
|
81
|
+
throw new TypeError(`exit watcher ${name} is invalid`);
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
function delay(milliseconds) {
|
|
85
|
+
return new Promise(resolvePromise => setTimeout(resolvePromise, milliseconds));
|
|
86
|
+
}
|
|
87
|
+
function ignoreSignal() { }
|
|
88
|
+
const invokedPath = process.argv[1] ? resolve(process.argv[1]) : null;
|
|
89
|
+
if (invokedPath === fileURLToPath(import.meta.url)) {
|
|
90
|
+
void runExitWatcher(process.argv.slice(2)).then(code => process.exit(code), error => {
|
|
91
|
+
process.stderr.write(`a1 exit watcher: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
92
|
+
process.exit(2);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { LaunchInstanceStopIntent, LaunchProfileId, SupervisorCommand } from "../lifecycle/index.js";
|
|
2
2
|
import { type SessionSelection } from "../lifecycle/index.js";
|
|
3
|
-
import { type NativeProcessInspector, type ProcessContainment } from "../process-containment/index.js";
|
|
3
|
+
import { type NativeProcessInspector, type ProcessContainment, type ProcessGuardianAddon } from "../process-containment/index.js";
|
|
4
4
|
interface GuardianControl {
|
|
5
5
|
connect(endpoint: string, timeoutMs?: number): Promise<unknown>;
|
|
6
6
|
command(command: SupervisorCommand): Promise<{
|
|
@@ -32,6 +32,7 @@ export interface LaunchGuardianOptions {
|
|
|
32
32
|
readonly inspector?: NativeProcessInspector;
|
|
33
33
|
readonly control?: GuardianControl;
|
|
34
34
|
readonly containment?: ProcessContainment;
|
|
35
|
+
readonly addon?: ProcessGuardianAddon;
|
|
35
36
|
readonly ensureHelper?: (path: string) => Promise<void>;
|
|
36
37
|
}
|
|
37
38
|
export declare function runLaunchGuardian(options: LaunchGuardianOptions): Promise<number>;
|