@timurproko/a1 0.2.7-dev.792 → 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 -0
- package/dist/app/session-shell/session-shell.d.ts +1 -0
- package/dist/app/session-shell/session-shell.js +10 -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/host-entry.d.ts +1 -0
- package/dist/foundation/resident-tabs/host-entry.js +17 -0
- package/dist/foundation/resident-tabs/index.d.ts +2 -2
- package/dist/foundation/resident-tabs/index.js +1 -1
- package/dist/foundation/resident-tabs/maintenance.d.ts +5 -4
- package/dist/foundation/resident-tabs/maintenance.js +32 -17
- package/dist/foundation/resident-tabs/release-holds.d.ts +3 -2
- package/dist/foundation/resident-tabs/release-holds.js +16 -7
- package/dist/foundation/resident-tabs/settings.d.ts +0 -1
- package/dist/foundation/resident-tabs/settings.js +1 -7
- package/dist/foundation/resident-tabs/terminal-host-artifact.d.ts +11 -15
- package/dist/foundation/resident-tabs/terminal-host-artifact.js +57 -32
- package/dist/foundation/session-writer-lock/index.d.ts +1 -3
- package/dist/foundation/session-writer-lock/index.js +8 -6
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +2 -1
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +2 -0
- package/dist/native/darwin-arm64/manifest.json +8 -6
- package/dist/native/darwin-arm64/process-guardian.node +0 -0
- package/dist/native/darwin-arm64/terminal-host-THIRD-PARTY-NOTICES.txt +1 -1
- package/dist/native/darwin-arm64/terminal-host.manifest.json +14 -13
- package/dist/native/darwin-arm64/terminal-host.node +0 -0
- package/dist/native/darwin-x64/manifest.json +9 -7
- package/dist/native/darwin-x64/process-guardian.node +0 -0
- package/dist/native/darwin-x64/terminal-host-THIRD-PARTY-NOTICES.txt +1 -1
- package/dist/native/darwin-x64/terminal-host.manifest.json +15 -14
- package/dist/native/darwin-x64/terminal-host.node +0 -0
- package/dist/native/linux-x64/manifest.json +8 -6
- package/dist/native/linux-x64/process-guardian.node +0 -0
- package/dist/native/linux-x64/terminal-host-THIRD-PARTY-NOTICES.txt +1 -1
- package/dist/native/linux-x64/terminal-host.manifest.json +14 -13
- package/dist/native/linux-x64/{terminal-host → terminal-host.node} +0 -0
- package/dist/native/win32-x64/manifest.json +8 -6
- package/dist/native/win32-x64/process-guardian.node +0 -0
- package/dist/native/win32-x64/terminal-host-THIRD-PARTY-NOTICES.txt +1 -1
- package/dist/native/win32-x64/terminal-host.manifest.json +14 -13
- package/dist/native/win32-x64/{terminal-host.exe → terminal-host.node} +0 -0
- package/dist/product-identity.d.ts +1 -1
- package/dist/product-identity.js +1 -1
- package/dist/product-identity.json +3 -2
- package/dist/runtime-payload-inventory.json +1 -0
- package/dist/ui/settings/declarations.d.ts +0 -12
- package/dist/ui/settings/declarations.js +0 -10
- package/docs/architecture/boundaries.md +10 -9
- package/docs/architecture/process-guardian-provenance.md +17 -11
- package/docs/architecture/resident-tabs.md +36 -34
- package/docs/architecture/terminal-host-provenance.md +5 -3
- package/docs/architecture/toolchain.md +6 -6
- package/docs/openspec-archive-automation.md +3 -3
- package/docs/validation.md +5 -1
- package/package.json +1 -1
- package/dist/foundation/process-containment/darwin-process-inspector.d.ts +0 -5
- package/dist/foundation/process-containment/darwin-process-inspector.js +0 -7
- package/dist/foundation/process-containment/native-guardian-containment.js +0 -152
- package/dist/foundation/process-containment/windows-process-inspector.d.ts +0 -19
- package/dist/foundation/process-containment/windows-process-inspector.js +0 -56
- package/dist/native/darwin-arm64/a1_session_lock.node +0 -0
- package/dist/native/darwin-arm64/process-guardian +0 -0
- package/dist/native/darwin-arm64/terminal-host +0 -0
- package/dist/native/darwin-x64/a1_session_lock.node +0 -0
- package/dist/native/darwin-x64/process-guardian +0 -0
- package/dist/native/darwin-x64/terminal-host +0 -0
- package/dist/native/linux-x64/a1_session_lock.node +0 -0
- package/dist/native/linux-x64/process-guardian +0 -0
- package/dist/native/win32-x64/a1_session_lock.node +0 -0
- package/dist/native/win32-x64/process-guardian.exe +0 -0
|
@@ -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;
|
|
@@ -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
|
/**
|
|
@@ -189,6 +189,7 @@ export class OwnedUiSessionPresenter {
|
|
|
189
189
|
}),
|
|
190
190
|
enableDockInputReuse: inputPresentation?.viewportReuse !== false,
|
|
191
191
|
footerSessionName: this.#residentTabOptions === undefined,
|
|
192
|
+
hideSettingsCommand: this.#residentTabOptions !== undefined,
|
|
192
193
|
persistentHistory: this.#customViewport && promptHistory !== undefined,
|
|
193
194
|
...(promptHistory === undefined ? {} : { historyEditor: promptHistory.editor }),
|
|
194
195
|
onSubmit: text => { void this.submit(text).catch(() => this.#reportSubmissionError()); },
|
|
@@ -1343,6 +1344,8 @@ export class OwnedUiSessionPresenter {
|
|
|
1343
1344
|
return { outcome: "completed", diagnostic: null };
|
|
1344
1345
|
}
|
|
1345
1346
|
if (request.command === "settings" && request.selection === undefined && request.confirmed === undefined) {
|
|
1347
|
+
if (this.#residentTabOptions !== undefined)
|
|
1348
|
+
return rejected("Settings is available from the global Settings tab.");
|
|
1346
1349
|
this.showSettingsSelector();
|
|
1347
1350
|
return { outcome: "completed", diagnostic: null };
|
|
1348
1351
|
}
|
|
@@ -1717,6 +1720,7 @@ export class OwnedUiSessionPresenter {
|
|
|
1717
1720
|
void onClosed?.()?.catch(() => undefined);
|
|
1718
1721
|
}
|
|
1719
1722
|
catch { /* Rationale: a failed acknowledgement stays pending. */ }
|
|
1723
|
+
this.#residentTabOptions?.closeOwnedRoute?.(route);
|
|
1720
1724
|
}
|
|
1721
1725
|
},
|
|
1722
1726
|
});
|
|
@@ -1764,6 +1768,9 @@ export class OwnedUiSessionPresenter {
|
|
|
1764
1768
|
});
|
|
1765
1769
|
this.#dialogId = dialog.id;
|
|
1766
1770
|
}
|
|
1771
|
+
openOwnedRoute(route) {
|
|
1772
|
+
this.#openOwnedRoute(route);
|
|
1773
|
+
}
|
|
1767
1774
|
hotkeysPresentation() {
|
|
1768
1775
|
const presentation = this.root.hotkeysPresentation();
|
|
1769
1776
|
return this.#residentTabOptions === undefined ? presentation : { ...presentation, residentTabs: true };
|
|
@@ -1773,6 +1780,9 @@ export class OwnedUiSessionPresenter {
|
|
|
1773
1780
|
const separator = body.search(/\s/);
|
|
1774
1781
|
const name = separator < 0 ? body : body.slice(0, separator);
|
|
1775
1782
|
const argument = separator < 0 ? "" : body.slice(separator + 1).trimStart();
|
|
1783
|
+
if (name === "settings" && this.#residentTabOptions !== undefined) {
|
|
1784
|
+
return rejected("Settings is available from the global Settings tab.");
|
|
1785
|
+
}
|
|
1776
1786
|
if (this.#routeHost?.claims(name))
|
|
1777
1787
|
return this.#openOwnedRoute(name);
|
|
1778
1788
|
if (this.#isWorkflowRoute(name))
|
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>;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
2
4
|
import { PRODUCT_TEXT } from "../../product-identity.js";
|
|
3
5
|
import { readLaunchContext } from "../launch-context/index.js";
|
|
4
6
|
import { resolve } from "node:path";
|
|
5
7
|
import { assertLaunchProfileId, resolveCohortEndpoint, resolveProductPaths, sessionSelectionArguments } from "../lifecycle/index.js";
|
|
6
|
-
import {
|
|
8
|
+
import { AddonContainment, AddonProcessInspector, closeVerifiedContainment, LinuxNativeProcessInspector, loadProcessGuardianAddon, resolveProcessGuardianPath, verifyProcessGuardianArtifact, } from "../process-containment/index.js";
|
|
7
9
|
import { SupervisorClient } from "../protocol/index.js";
|
|
8
10
|
import { markStartupPhase } from "../startup/index.js";
|
|
11
|
+
import { exitWatcherArguments } from "./exit-watcher.js";
|
|
9
12
|
export async function runLaunchGuardian(options) {
|
|
10
13
|
assertLaunchProfileId(options.profileId);
|
|
11
14
|
const sessionArgs = sessionSelectionArguments(options.sessionSelection);
|
|
@@ -15,8 +18,11 @@ export async function runLaunchGuardian(options) {
|
|
|
15
18
|
const paths = resolveProductPaths(environment);
|
|
16
19
|
const instanceId = randomUUID();
|
|
17
20
|
const helperPath = options.helperPath ?? resolveProcessGuardianPath(options.releaseRoot, environment);
|
|
18
|
-
const inspector = options.inspector ?? platformInspector(helperPath);
|
|
19
21
|
await (options.ensureHelper ?? verifyProcessGuardianArtifact)(helperPath);
|
|
22
|
+
// Rationale: tests that inject both process boundaries need no native module; every production
|
|
23
|
+
// path loads the exact artifact only after its manifest and digest have been verified.
|
|
24
|
+
const addon = options.addon ?? (options.containment && options.inspector ? null : loadProcessGuardianAddon(helperPath));
|
|
25
|
+
const inspector = options.inspector ?? platformInspector(requireAddon(addon));
|
|
20
26
|
const guardianIdentity = await inspector.observe(process.pid);
|
|
21
27
|
if (!guardianIdentity)
|
|
22
28
|
throw diagnosticError("launch guardian cannot verify its own native process identity", "PROCESS_IDENTITY_UNAVAILABLE");
|
|
@@ -30,7 +36,8 @@ export async function runLaunchGuardian(options) {
|
|
|
30
36
|
: paths.endpoint;
|
|
31
37
|
await client.connect(endpoint);
|
|
32
38
|
await markStartupPhase(environment, "guardian-connected");
|
|
33
|
-
const
|
|
39
|
+
const exitNoticePath = resolve(paths.runtimeDir, "launch-instances", `${instanceId}.exit-notice`);
|
|
40
|
+
const containment = options.containment ?? new AddonContainment(instanceId, helperPath, exitNoticePath, requireAddon(addon));
|
|
34
41
|
const stop = stopRequests(client, instanceId);
|
|
35
42
|
let rootIdentity = null;
|
|
36
43
|
try {
|
|
@@ -43,14 +50,26 @@ export async function runLaunchGuardian(options) {
|
|
|
43
50
|
guardianIdentity,
|
|
44
51
|
});
|
|
45
52
|
let handle;
|
|
53
|
+
let encodedModes = null;
|
|
46
54
|
try {
|
|
47
55
|
const rootCommand = options.rootCommand ?? { executable: process.execPath, arguments: [options.uiEntry, ...sessionArgs] };
|
|
56
|
+
encodedModes = addon?.captureTerminalModes() ?? null;
|
|
48
57
|
handle = await containment.spawn(rootCommand.executable, [...rootCommand.arguments], {
|
|
49
58
|
cwd: options.cwd ?? process.cwd(),
|
|
50
59
|
environment: environmentEntries(environment),
|
|
51
60
|
...(environment.TERM ? { terminalType: environment.TERM } : {}),
|
|
52
61
|
});
|
|
53
62
|
rootIdentity = handle.identity;
|
|
63
|
+
if (addon && encodedModes !== null) {
|
|
64
|
+
await startExitWatcher(addon, {
|
|
65
|
+
addonPath: helperPath,
|
|
66
|
+
noticePath: exitNoticePath,
|
|
67
|
+
encodedModes,
|
|
68
|
+
runtimePid: handle.identity.pid,
|
|
69
|
+
processGroup: handle.processGroup ?? null,
|
|
70
|
+
foregroundGroup: handle.foregroundGroup ?? null,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
54
73
|
await requireAccepted(client, {
|
|
55
74
|
type: "activate-launch-instance",
|
|
56
75
|
requestId: randomUUID(),
|
|
@@ -89,6 +108,10 @@ export async function runLaunchGuardian(options) {
|
|
|
89
108
|
return result.outcome;
|
|
90
109
|
}),
|
|
91
110
|
]);
|
|
111
|
+
// Invariant: the root removes a clean notice. If it left the notice armed, the guardian claims,
|
|
112
|
+
// restores, and prints it before closing the native containment handle.
|
|
113
|
+
if (addon && encodedModes !== null)
|
|
114
|
+
addon.deliverExitNotice(exitNoticePath, encodedModes);
|
|
92
115
|
await containment.close();
|
|
93
116
|
if (outcome.kind !== "interrupted" || outcome.reason !== "supervisor-disconnect") {
|
|
94
117
|
await completeBestEffort(client, instanceId, outcome.kind === "interrupted" || outcome.kind === "cleanup-error" ? "interrupted" : "completed", outcome);
|
|
@@ -104,15 +127,44 @@ export async function runLaunchGuardian(options) {
|
|
|
104
127
|
client.close();
|
|
105
128
|
}
|
|
106
129
|
}
|
|
107
|
-
function
|
|
130
|
+
async function startExitWatcher(addon, options) {
|
|
131
|
+
const entry = fileURLToPath(new URL("./exit-watcher.js", import.meta.url));
|
|
132
|
+
const arguments_ = [entry, ...exitWatcherArguments({
|
|
133
|
+
guardianPid: process.pid,
|
|
134
|
+
...options,
|
|
135
|
+
})];
|
|
136
|
+
if (process.platform === "win32") {
|
|
137
|
+
addon.spawnWatcher({ executable: process.execPath, arguments: arguments_ });
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
141
|
+
const watcher = spawn(process.execPath, arguments_, {
|
|
142
|
+
stdio: "inherit",
|
|
143
|
+
detached: false,
|
|
144
|
+
windowsHide: false,
|
|
145
|
+
});
|
|
146
|
+
watcher.once("error", rejectPromise);
|
|
147
|
+
watcher.once("spawn", () => {
|
|
148
|
+
watcher.off("error", rejectPromise);
|
|
149
|
+
watcher.unref();
|
|
150
|
+
resolvePromise();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function platformInspector(addon) {
|
|
108
155
|
if (process.platform === "win32")
|
|
109
|
-
return new
|
|
156
|
+
return new AddonProcessInspector(addon, "windows-filetime:");
|
|
110
157
|
if (process.platform === "linux")
|
|
111
158
|
return new LinuxNativeProcessInspector();
|
|
112
159
|
if (process.platform === "darwin")
|
|
113
|
-
return new
|
|
160
|
+
return new AddonProcessInspector(addon, "darwin-proc-start:");
|
|
114
161
|
throw diagnosticError(`launch-instance process containment is unsupported on ${process.platform}`, "CONTAINMENT_UNSUPPORTED");
|
|
115
162
|
}
|
|
163
|
+
function requireAddon(addon) {
|
|
164
|
+
if (!addon)
|
|
165
|
+
throw diagnosticError("process guardian addon is unavailable", "CONTAINMENT_ARTIFACT_MISSING");
|
|
166
|
+
return addon;
|
|
167
|
+
}
|
|
116
168
|
function stopRequests(client, instanceId) {
|
|
117
169
|
let resolveStop;
|
|
118
170
|
let settled = false;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import type { NativeProcessIdentity, ProcessContainmentIdentity } from "../lifecycle/index.js";
|
|
2
|
+
import { type ProcessGuardianAddon } from "./addon.js";
|
|
2
3
|
import type { ContainedProcessHandle, ContainedSpawnOptions, ProcessContainment } from "./contracts.js";
|
|
3
|
-
/**
|
|
4
|
-
export declare class
|
|
4
|
+
/** Owns one native containment object in the launch guardian's Node process. */
|
|
5
|
+
export declare class AddonContainment implements ProcessContainment {
|
|
5
6
|
#private;
|
|
6
7
|
readonly instanceId: string;
|
|
7
|
-
readonly
|
|
8
|
-
readonly statusPath: string;
|
|
8
|
+
readonly addonPath: string;
|
|
9
9
|
readonly exitNoticePath: string | null;
|
|
10
|
-
|
|
10
|
+
readonly addon: ProcessGuardianAddon;
|
|
11
|
+
private readonly platform;
|
|
12
|
+
constructor(instanceId: string, addonPath: string, exitNoticePath?: string | null, addon?: ProcessGuardianAddon, platform?: NodeJS.Platform);
|
|
11
13
|
get identity(): ProcessContainmentIdentity;
|
|
12
14
|
spawn(executable: string, arguments_: readonly string[], options: ContainedSpawnOptions): Promise<ContainedProcessHandle>;
|
|
13
15
|
contains(identity: NativeProcessIdentity): Promise<boolean>;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { constants } from "node:os";
|
|
2
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { PRODUCT_IDENTITY } from "../../product-identity.js";
|
|
5
|
+
import { loadProcessGuardianAddon } from "./addon.js";
|
|
6
|
+
/** Owns one native containment object in the launch guardian's Node process. */
|
|
7
|
+
export class AddonContainment {
|
|
8
|
+
#identity;
|
|
9
|
+
#rootIdentity = null;
|
|
10
|
+
#runtime = null;
|
|
11
|
+
#outcome = null;
|
|
12
|
+
#settled = false;
|
|
13
|
+
instanceId;
|
|
14
|
+
addonPath;
|
|
15
|
+
exitNoticePath;
|
|
16
|
+
addon;
|
|
17
|
+
platform;
|
|
18
|
+
constructor(instanceId, addonPath, exitNoticePath = null, addon = loadProcessGuardianAddon(addonPath), platform = process.platform) {
|
|
19
|
+
this.instanceId = instanceId;
|
|
20
|
+
this.addonPath = addonPath;
|
|
21
|
+
this.exitNoticePath = exitNoticePath;
|
|
22
|
+
this.addon = addon;
|
|
23
|
+
this.platform = platform;
|
|
24
|
+
this.#identity = { provider: "native-addon-pending", token: instanceId };
|
|
25
|
+
}
|
|
26
|
+
get identity() {
|
|
27
|
+
return this.#identity;
|
|
28
|
+
}
|
|
29
|
+
async spawn(executable, arguments_, options) {
|
|
30
|
+
if (this.#runtime)
|
|
31
|
+
throw new Error("process containment already has a root runtime");
|
|
32
|
+
if (this.exitNoticePath) {
|
|
33
|
+
await mkdir(dirname(this.exitNoticePath), { recursive: true, mode: 0o700 });
|
|
34
|
+
await rm(this.exitNoticePath, { force: true });
|
|
35
|
+
}
|
|
36
|
+
const runtime = this.addon.spawnContained({
|
|
37
|
+
executable,
|
|
38
|
+
arguments: [...arguments_],
|
|
39
|
+
cwd: options.cwd,
|
|
40
|
+
environment: {
|
|
41
|
+
...options.environment,
|
|
42
|
+
...(options.terminalType ? { TERM: options.terminalType } : {}),
|
|
43
|
+
...(this.exitNoticePath ? { [PRODUCT_IDENTITY.environment.exitNoticePath]: this.exitNoticePath } : {}),
|
|
44
|
+
},
|
|
45
|
+
instanceId: this.instanceId,
|
|
46
|
+
});
|
|
47
|
+
assertRuntime(runtime);
|
|
48
|
+
this.#runtime = runtime;
|
|
49
|
+
this.#rootIdentity = { pid: runtime.pid, startIdentity: runtime.startIdentity };
|
|
50
|
+
this.#identity = { provider: runtime.containmentProvider, token: runtime.containmentToken };
|
|
51
|
+
const outcome = runtime.wait().then(exitCode => outcomeFromExitCode(exitCode, this.platform), error => ({
|
|
52
|
+
kind: "guardian-error",
|
|
53
|
+
message: errorMessage(error),
|
|
54
|
+
code: errorCode(error),
|
|
55
|
+
})).finally(() => { this.#settled = true; });
|
|
56
|
+
this.#outcome = outcome;
|
|
57
|
+
return {
|
|
58
|
+
identity: this.#rootIdentity,
|
|
59
|
+
outcome,
|
|
60
|
+
...(runtime.processGroup === null ? {} : { processGroup: runtime.processGroup }),
|
|
61
|
+
...(runtime.foregroundGroup === null ? {} : { foregroundGroup: runtime.foregroundGroup }),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async contains(identity) {
|
|
65
|
+
return this.#rootIdentity !== null
|
|
66
|
+
&& identity.pid === this.#rootIdentity.pid
|
|
67
|
+
&& identity.startIdentity === this.#rootIdentity.startIdentity;
|
|
68
|
+
}
|
|
69
|
+
async stop(force) {
|
|
70
|
+
const runtime = this.#runtime;
|
|
71
|
+
if (!runtime || this.#settled)
|
|
72
|
+
return;
|
|
73
|
+
try {
|
|
74
|
+
if (force)
|
|
75
|
+
runtime.terminate(137);
|
|
76
|
+
else if (this.platform === "win32")
|
|
77
|
+
process.kill(runtime.pid, "SIGTERM");
|
|
78
|
+
else
|
|
79
|
+
runtime.stop();
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (errorCode(error) !== "ESRCH")
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async waitForEmpty(timeoutMs) {
|
|
87
|
+
if (this.#settled || !this.#outcome)
|
|
88
|
+
return true;
|
|
89
|
+
return await Promise.race([
|
|
90
|
+
this.#outcome.then(() => true),
|
|
91
|
+
new Promise(resolvePromise => {
|
|
92
|
+
const timer = setTimeout(() => resolvePromise(false), timeoutMs);
|
|
93
|
+
timer.unref();
|
|
94
|
+
}),
|
|
95
|
+
]);
|
|
96
|
+
}
|
|
97
|
+
async close() {
|
|
98
|
+
const runtime = this.#runtime;
|
|
99
|
+
if (!runtime)
|
|
100
|
+
return;
|
|
101
|
+
runtime.close();
|
|
102
|
+
await this.waitForEmpty(1_500);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function resolveProcessGuardianPath(releaseRoot, environment = process.env, platform = process.platform, architecture = process.arch) {
|
|
106
|
+
const override = environment[PRODUCT_IDENTITY.environment.processGuardianPath]?.trim();
|
|
107
|
+
if (override)
|
|
108
|
+
return resolve(override);
|
|
109
|
+
return resolve(releaseRoot, "dist", "native", `${platform}-${architecture}`, `${PRODUCT_IDENTITY.artifacts.processGuardianAddon}.node`);
|
|
110
|
+
}
|
|
111
|
+
function assertRuntime(runtime) {
|
|
112
|
+
if (!Number.isSafeInteger(runtime.pid) || runtime.pid <= 0
|
|
113
|
+
|| typeof runtime.startIdentity !== "string" || runtime.startIdentity.length === 0
|
|
114
|
+
|| typeof runtime.containmentProvider !== "string" || runtime.containmentProvider.length === 0
|
|
115
|
+
|| typeof runtime.containmentToken !== "string" || runtime.containmentToken.length === 0
|
|
116
|
+
|| (runtime.processGroup !== null && (!Number.isSafeInteger(runtime.processGroup) || runtime.processGroup <= 0))
|
|
117
|
+
|| (runtime.foregroundGroup !== null && (!Number.isSafeInteger(runtime.foregroundGroup) || runtime.foregroundGroup <= 0))) {
|
|
118
|
+
runtime.close();
|
|
119
|
+
throw Object.assign(new Error("process guardian addon returned invalid containment identity"), { code: "CONTAINMENT_ARTIFACT_INCOMPATIBLE" });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function outcomeFromExitCode(exitCode, platform) {
|
|
123
|
+
if (!Number.isSafeInteger(exitCode) || exitCode < 0 || exitCode > 255) {
|
|
124
|
+
return { kind: "guardian-error", message: "process guardian addon returned an invalid exit code", code: "CONTAINMENT_OUTCOME_INVALID" };
|
|
125
|
+
}
|
|
126
|
+
if (platform !== "win32" && exitCode >= 128) {
|
|
127
|
+
const signalNumber = exitCode - 128;
|
|
128
|
+
const signal = Object.entries(constants.signals).find(([, value]) => value === signalNumber)?.[0];
|
|
129
|
+
if (signal)
|
|
130
|
+
return { kind: "signaled", signal };
|
|
131
|
+
}
|
|
132
|
+
return { kind: "exited", exitCode };
|
|
133
|
+
}
|
|
134
|
+
function errorCode(error) {
|
|
135
|
+
return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : null;
|
|
136
|
+
}
|
|
137
|
+
function errorMessage(error) {
|
|
138
|
+
return error instanceof Error ? error.message : String(error);
|
|
139
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { NativeProcessIdentity } from "../lifecycle/index.js";
|
|
2
|
+
import type { ProcessGuardianAddon } from "./addon.js";
|
|
3
|
+
import type { NativeProcessInspector } from "./contracts.js";
|
|
4
|
+
/** Verifies native process identity through the process-guardian addon's bounded inspection call. */
|
|
5
|
+
export declare class AddonProcessInspector implements NativeProcessInspector {
|
|
6
|
+
readonly addon: Pick<ProcessGuardianAddon, "inspectProcessStart">;
|
|
7
|
+
readonly identityPrefix: string;
|
|
8
|
+
constructor(addon: Pick<ProcessGuardianAddon, "inspectProcessStart">, identityPrefix: string);
|
|
9
|
+
observe(pid: number): Promise<NativeProcessIdentity | null>;
|
|
10
|
+
matches(identity: NativeProcessIdentity): Promise<boolean>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Verifies native process identity through the process-guardian addon's bounded inspection call. */
|
|
2
|
+
export class AddonProcessInspector {
|
|
3
|
+
addon;
|
|
4
|
+
identityPrefix;
|
|
5
|
+
constructor(addon, identityPrefix) {
|
|
6
|
+
if (!identityPrefix || identityPrefix.includes("\0"))
|
|
7
|
+
throw new TypeError("process identity prefix is invalid");
|
|
8
|
+
this.addon = addon;
|
|
9
|
+
this.identityPrefix = identityPrefix;
|
|
10
|
+
}
|
|
11
|
+
async observe(pid) {
|
|
12
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
13
|
+
throw new TypeError("inspected PID must be a positive safe integer");
|
|
14
|
+
let startIdentity;
|
|
15
|
+
try {
|
|
16
|
+
startIdentity = this.addon.inspectProcessStart(pid);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
throw Object.assign(new Error(`cannot inspect native process identity: ${errorMessage(error)}`), { code: "PROCESS_INSPECTION_FAILED" });
|
|
20
|
+
}
|
|
21
|
+
if (startIdentity === null)
|
|
22
|
+
return null;
|
|
23
|
+
if (typeof startIdentity !== "string" || startIdentity.length === 0 || startIdentity.length > 512
|
|
24
|
+
|| startIdentity.includes("\0") || !startIdentity.startsWith(this.identityPrefix)) {
|
|
25
|
+
throw Object.assign(new Error("process guardian returned mismatched identity data"), { code: "PROCESS_INSPECTION_INVALID" });
|
|
26
|
+
}
|
|
27
|
+
return { pid, startIdentity };
|
|
28
|
+
}
|
|
29
|
+
async matches(identity) {
|
|
30
|
+
const observed = await this.observe(identity.pid);
|
|
31
|
+
return observed !== null && observed.startIdentity === identity.startIdentity;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function errorMessage(error) {
|
|
35
|
+
return error instanceof Error ? error.message : String(error);
|
|
36
|
+
}
|