@timurproko/a1 0.1.1-dev.5 → 0.1.1-dev.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -9
- package/bin/a1-guardian.js +22 -0
- package/bin/a1-ui.js +7 -4
- package/dist/native/darwin-arm64/a1-process-guardian +0 -0
- package/dist/native/darwin-arm64/manifest.json +20 -0
- package/dist/native/linux-x64/a1-process-guardian +0 -0
- package/dist/native/linux-x64/manifest.json +20 -0
- package/dist/native/win32-x64/a1-process-guardian.exe +0 -0
- package/dist/native/win32-x64/manifest.json +20 -0
- package/dist/src/composition/index.d.ts +26 -0
- package/dist/src/composition/index.js +88 -2
- package/dist/src/features/launch/runtime-selection.d.ts +2 -1
- package/dist/src/features/launch/runtime-selection.js +2 -2
- package/dist/src/features/owned-ui/index.d.ts +1 -0
- package/dist/src/features/owned-ui/index.js +1 -0
- package/dist/src/features/owned-ui/run.d.ts +6 -0
- package/dist/src/features/owned-ui/run.js +3 -1
- package/dist/src/features/owned-ui/settings-app.d.ts +22 -0
- package/dist/src/features/owned-ui/settings-app.js +631 -0
- package/dist/src/foundation/agent-engine-contracts/domain.d.ts +26 -0
- package/dist/src/foundation/launch-guardian/index.d.ts +1 -0
- package/dist/src/foundation/launch-guardian/index.js +1 -0
- package/dist/src/foundation/launch-guardian/main.d.ts +31 -0
- package/dist/src/foundation/launch-guardian/main.js +165 -0
- package/dist/src/foundation/lifecycle/commands.d.ts +40 -0
- package/dist/src/foundation/lifecycle/commands.js +1 -0
- package/dist/src/foundation/lifecycle/index.d.ts +2 -0
- package/dist/src/foundation/lifecycle/index.js +2 -0
- package/dist/src/foundation/lifecycle/launch-instance.d.ts +46 -0
- package/dist/src/foundation/lifecycle/launch-instance.js +134 -0
- package/dist/src/foundation/lifecycle/model.d.ts +6 -42
- package/dist/src/foundation/owned-ui-settings/declarations.d.ts +13 -0
- package/dist/src/foundation/owned-ui-settings/declarations.js +39 -0
- package/dist/src/foundation/owned-ui-settings/index.d.ts +6 -0
- package/dist/src/foundation/owned-ui-settings/index.js +6 -0
- package/dist/src/foundation/owned-ui-settings/migrations.d.ts +13 -0
- package/dist/src/foundation/owned-ui-settings/migrations.js +28 -0
- package/dist/src/foundation/owned-ui-settings/resolution.d.ts +45 -0
- package/dist/src/foundation/owned-ui-settings/resolution.js +147 -0
- package/dist/src/foundation/owned-ui-settings/sections.d.ts +70 -0
- package/dist/src/foundation/owned-ui-settings/sections.js +93 -0
- package/dist/src/foundation/owned-ui-settings/session.d.ts +44 -0
- package/dist/src/foundation/owned-ui-settings/session.js +145 -0
- package/dist/src/foundation/owned-ui-settings/store.d.ts +28 -0
- package/dist/src/foundation/owned-ui-settings/store.js +90 -0
- package/dist/src/foundation/pi-component-adapter/index.d.ts +1 -0
- package/dist/src/foundation/pi-component-adapter/index.js +1 -0
- package/dist/src/foundation/pi-component-adapter/shell-selectors-dialogs.d.ts +9 -1
- package/dist/src/foundation/pi-component-adapter/shell-selectors-dialogs.js +10 -8
- package/dist/src/foundation/pi-engine-adapter/adapter.d.ts +12 -3
- package/dist/src/foundation/pi-engine-adapter/adapter.js +73 -7
- package/dist/src/foundation/pi-engine-adapter/pi-settings-metadata.json +374 -0
- package/dist/src/foundation/pi-engine-adapter/public-main-entry.js +10 -1
- package/dist/src/foundation/pi-engine-adapter/settings-integration.d.ts +42 -2
- package/dist/src/foundation/pi-engine-adapter/settings-integration.js +185 -24
- package/dist/src/foundation/pi-engine-adapter/workflows.d.ts +9 -0
- package/dist/src/foundation/pi-owned-ui-integration/index.d.ts +1 -0
- package/dist/src/foundation/pi-owned-ui-integration/index.js +1 -0
- package/dist/src/foundation/pi-owned-ui-integration/route-host.d.ts +29 -0
- package/dist/src/foundation/pi-owned-ui-integration/route-host.js +1 -0
- package/dist/src/foundation/pi-owned-ui-integration/session-shell.d.ts +6 -0
- package/dist/src/foundation/pi-owned-ui-integration/session-shell.js +71 -1
- package/dist/src/foundation/pi-tui-runtime-adapter/adapter.d.ts +6 -0
- package/dist/src/foundation/pi-tui-runtime-adapter/adapter.js +9 -0
- package/dist/src/foundation/process-containment/artifact.d.ts +1 -0
- package/dist/src/foundation/process-containment/artifact.js +34 -0
- package/dist/src/foundation/process-containment/contracts.d.ts +31 -0
- package/dist/src/foundation/process-containment/contracts.js +35 -0
- package/dist/src/foundation/process-containment/index.d.ts +5 -0
- package/dist/src/foundation/process-containment/index.js +5 -0
- package/dist/src/foundation/process-containment/linux-process-inspector.d.ts +6 -0
- package/dist/src/foundation/process-containment/linux-process-inspector.js +26 -0
- package/dist/src/foundation/process-containment/native-guardian-containment.d.ts +16 -0
- package/dist/src/foundation/process-containment/native-guardian-containment.js +139 -0
- package/dist/src/foundation/process-containment/windows-process-inspector.d.ts +17 -0
- package/dist/src/foundation/process-containment/windows-process-inspector.js +53 -0
- package/dist/src/foundation/protocol/client.d.ts +2 -1
- package/dist/src/foundation/protocol/client.js +3 -0
- package/dist/src/foundation/protocol/messages.d.ts +5 -5
- package/dist/src/foundation/protocol/messages.js +1 -1
- package/dist/src/foundation/release/bootstrap.js +29 -10
- package/dist/src/foundation/release/cohort-selection.js +3 -3
- package/dist/src/foundation/release/cohort-state.d.ts +3 -2
- package/dist/src/foundation/release/process-cleanup.d.ts +1 -1
- package/dist/src/foundation/release/process-cleanup.js +2 -2
- package/dist/src/foundation/release/update.js +2 -2
- package/dist/src/foundation/storage/control-store.d.ts +8 -7
- package/dist/src/foundation/storage/control-store.js +105 -55
- package/dist/src/foundation/supervision/server.d.ts +4 -2
- package/dist/src/foundation/supervision/server.js +254 -87
- package/dist/src/foundation/transparent-terminal/command-resolution.js +6 -3
- package/dist/src/foundation/transparent-terminal/foreground-broker.d.ts +5 -13
- package/dist/src/foundation/transparent-terminal/foreground-broker.js +11 -68
- package/dist/src/foundation/transparent-terminal/main.js +1 -8
- package/dist/src/foundation/ui-apps/contracts.d.ts +42 -0
- package/dist/src/foundation/ui-apps/contracts.js +12 -0
- package/dist/src/foundation/ui-apps/host.d.ts +42 -0
- package/dist/src/foundation/ui-apps/host.js +152 -0
- package/dist/src/foundation/ui-apps/index.d.ts +3 -0
- package/dist/src/foundation/ui-apps/index.js +3 -0
- package/dist/src/foundation/ui-apps/registry.d.ts +11 -0
- package/dist/src/foundation/ui-apps/registry.js +32 -0
- package/dist/src/foundation/ui-components/dialog-panel.d.ts +36 -0
- package/dist/src/foundation/ui-components/dialog-panel.js +47 -0
- package/dist/src/foundation/ui-components/frame.d.ts +21 -0
- package/dist/src/foundation/ui-components/frame.js +54 -0
- package/dist/src/foundation/ui-components/index.d.ts +18 -0
- package/dist/src/foundation/ui-components/index.js +18 -0
- package/dist/src/foundation/ui-components/label.d.ts +8 -0
- package/dist/src/foundation/ui-components/label.js +36 -0
- package/dist/src/foundation/ui-components/line-input.d.ts +74 -0
- package/dist/src/foundation/ui-components/line-input.js +245 -0
- package/dist/src/foundation/ui-components/list-block.d.ts +66 -0
- package/dist/src/foundation/ui-components/list-block.js +176 -0
- package/dist/src/foundation/ui-components/list-view.d.ts +66 -0
- package/dist/src/foundation/ui-components/list-view.js +76 -0
- package/dist/src/foundation/ui-components/mouse.d.ts +15 -0
- package/dist/src/foundation/ui-components/mouse.js +46 -0
- package/dist/src/foundation/ui-components/pane.d.ts +33 -0
- package/dist/src/foundation/ui-components/pane.js +7 -0
- package/dist/src/foundation/ui-components/revision.d.ts +35 -0
- package/dist/src/foundation/ui-components/revision.js +79 -0
- package/dist/src/foundation/ui-components/scrollbar.d.ts +62 -0
- package/dist/src/foundation/ui-components/scrollbar.js +102 -0
- package/dist/src/foundation/ui-components/shortcuts.d.ts +57 -0
- package/dist/src/foundation/ui-components/shortcuts.js +81 -0
- package/dist/src/foundation/ui-components/spans.d.ts +7 -0
- package/dist/src/foundation/ui-components/spans.js +59 -0
- package/dist/src/foundation/ui-components/status-line.d.ts +16 -0
- package/dist/src/foundation/ui-components/status-line.js +14 -0
- package/dist/src/foundation/ui-components/stepper.d.ts +24 -0
- package/dist/src/foundation/ui-components/stepper.js +33 -0
- package/dist/src/foundation/ui-components/surface.d.ts +20 -0
- package/dist/src/foundation/ui-components/surface.js +38 -0
- package/dist/src/foundation/ui-components/text.d.ts +24 -0
- package/dist/src/foundation/ui-components/text.js +123 -0
- package/dist/src/foundation/ui-components/theme.d.ts +17 -0
- package/dist/src/foundation/ui-components/theme.js +12 -0
- package/dist/src/foundation/ui-components/value-menu.d.ts +41 -0
- package/dist/src/foundation/ui-components/value-menu.js +33 -0
- package/dist/src/product-identity.d.ts +2 -2
- package/dist/src/product-identity.js +2 -1
- package/dist/src/product-identity.json +3 -0
- package/docs/architecture/boundaries.md +31 -13
- package/docs/architecture/process-guardian-provenance.md +26 -0
- package/docs/architecture/toolchain.md +6 -0
- package/docs/architecture/ui-reference-provenance.md +50 -0
- package/docs/ci-release-runbook.md +38 -42
- package/docs/features/launch-profiles.md +7 -1
- package/docs/manual-launch-instance-acceptance.md +66 -0
- package/package.json +11 -12
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
import type { SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { AgentJsonValue, AgentSettingDescriptor, AgentSettingsPort } from "../agent-engine-contracts/index.js";
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Values only the running engine knows: the themes installed on this machine and
|
|
5
|
+
* the thinking levels this session supports. The boundary asks for them as it is
|
|
6
|
+
* read, so no layer above has to know they are not fixed.
|
|
7
|
+
*/
|
|
8
|
+
export interface PiSettingsProviders {
|
|
9
|
+
readonly themes?: () => readonly string[];
|
|
10
|
+
readonly thinkingLevels?: () => readonly string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The engine stores a theme as one string with two forms: a theme's name, or a
|
|
14
|
+
* `light/dark` pair meaning "follow the terminal". That grammar is the engine's,
|
|
15
|
+
* so it is parsed and composed here and reaches the surfaces above as ordinary
|
|
16
|
+
* settings — a theme entry that offers `automatic`, and, while automatic, one
|
|
17
|
+
* entry per terminal appearance.
|
|
18
|
+
*/
|
|
19
|
+
export declare const AUTOMATIC_THEME = "automatic";
|
|
20
|
+
export declare const LIGHT_THEME_KEY = "themeLight";
|
|
21
|
+
export declare const DARK_THEME_KEY = "themeDark";
|
|
22
|
+
interface AutomaticTheme {
|
|
23
|
+
readonly light: string;
|
|
24
|
+
readonly dark: string;
|
|
25
|
+
}
|
|
26
|
+
/** A stored theme is automatic when it names one theme per terminal appearance. */
|
|
27
|
+
export declare function parseAutomaticTheme(stored: string): AutomaticTheme | null;
|
|
28
|
+
/**
|
|
29
|
+
* The settings A1 exposes: the ones the engine presents. Kept as the engine's
|
|
30
|
+
* own inventory rather than a list beside it, so a setting it adds or renames
|
|
31
|
+
* shows up as a named test failure instead of quietly going missing.
|
|
32
|
+
*/
|
|
33
|
+
export declare const EXPOSED_SETTING_KEYS: readonly string[];
|
|
34
|
+
/**
|
|
35
|
+
* What has drifted between the settings the engine presents and the settings A1
|
|
36
|
+
* maps to its API: the ones A1 has not caught up with, and the ones it kept
|
|
37
|
+
* after the engine dropped them. Either is a build failure with a name in it.
|
|
38
|
+
*/
|
|
39
|
+
export declare function settingsInventoryDrift(presented: readonly string[], mapped: readonly string[]): {
|
|
40
|
+
readonly unmapped: readonly string[];
|
|
41
|
+
readonly stale: readonly string[];
|
|
42
|
+
};
|
|
4
43
|
export declare class PiSettingsIntegration implements AgentSettingsPort {
|
|
5
44
|
#private;
|
|
6
45
|
private readonly settings;
|
|
@@ -8,10 +47,11 @@ export declare class PiSettingsIntegration implements AgentSettingsPort {
|
|
|
8
47
|
write: boolean;
|
|
9
48
|
flush: boolean;
|
|
10
49
|
};
|
|
11
|
-
constructor(settings: SettingsManager);
|
|
50
|
+
constructor(settings: SettingsManager, providers?: PiSettingsProviders);
|
|
12
51
|
listSettings(): Promise<readonly AgentSettingDescriptor[]>;
|
|
13
52
|
readSetting(key: string): Promise<AgentJsonValue | undefined>;
|
|
14
53
|
writeSetting(key: string, value: AgentJsonValue): Promise<void>;
|
|
15
54
|
writeSettingNow(key: string, value: AgentJsonValue): void;
|
|
16
55
|
flush(): Promise<void>;
|
|
17
56
|
}
|
|
57
|
+
export {};
|
|
@@ -1,30 +1,177 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import piSettingsMetadata from "./pi-settings-metadata.json" with { type: "json" };
|
|
2
|
+
/**
|
|
3
|
+
* The engine stores a theme as one string with two forms: a theme's name, or a
|
|
4
|
+
* `light/dark` pair meaning "follow the terminal". That grammar is the engine's,
|
|
5
|
+
* so it is parsed and composed here and reaches the surfaces above as ordinary
|
|
6
|
+
* settings — a theme entry that offers `automatic`, and, while automatic, one
|
|
7
|
+
* entry per terminal appearance.
|
|
8
|
+
*/
|
|
9
|
+
export const AUTOMATIC_THEME = "automatic";
|
|
10
|
+
export const LIGHT_THEME_KEY = "themeLight";
|
|
11
|
+
export const DARK_THEME_KEY = "themeDark";
|
|
12
|
+
const THEME_KEY = "theme";
|
|
13
|
+
const THINKING_KEY = "thinkingLevel";
|
|
14
|
+
/** A stored theme is automatic when it names one theme per terminal appearance. */
|
|
15
|
+
export function parseAutomaticTheme(stored) {
|
|
16
|
+
const at = stored.indexOf("/");
|
|
17
|
+
if (at < 0 || stored.indexOf("/", at + 1) >= 0)
|
|
18
|
+
return null;
|
|
19
|
+
const light = stored.slice(0, at).trim();
|
|
20
|
+
const dark = stored.slice(at + 1).trim();
|
|
21
|
+
return light.length > 0 && dark.length > 0 ? { light, dark } : null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* How the engine presents these settings — wording, order, and the flags a
|
|
25
|
+
* dialog-backed setting offers — generated from its own source by
|
|
26
|
+
* `npm run update:pi-settings-metadata` and verified by a governance test, so a
|
|
27
|
+
* Pi upgrade cannot silently reword or reorder what A1 shows.
|
|
28
|
+
*/
|
|
29
|
+
const PRESENTATION = piSettingsMetadata;
|
|
30
|
+
/**
|
|
31
|
+
* The settings A1 exposes: the ones the engine presents. Kept as the engine's
|
|
32
|
+
* own inventory rather than a list beside it, so a setting it adds or renames
|
|
33
|
+
* shows up as a named test failure instead of quietly going missing.
|
|
34
|
+
*/
|
|
35
|
+
export const EXPOSED_SETTING_KEYS = PRESENTATION.presented;
|
|
36
|
+
/**
|
|
37
|
+
* What has drifted between the settings the engine presents and the settings A1
|
|
38
|
+
* maps to its API: the ones A1 has not caught up with, and the ones it kept
|
|
39
|
+
* after the engine dropped them. Either is a build failure with a name in it.
|
|
40
|
+
*/
|
|
41
|
+
export function settingsInventoryDrift(presented, mapped) {
|
|
42
|
+
const has = new Set(mapped);
|
|
43
|
+
const engine = new Set(presented);
|
|
44
|
+
return {
|
|
45
|
+
unmapped: presented.filter(key => !has.has(key)),
|
|
46
|
+
stale: mapped.filter(key => !engine.has(key)),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** What the engine offers for a setting, as the strings it words them with. */
|
|
50
|
+
/** One appearance of an automatic theme, offered as an ordinary setting. */
|
|
51
|
+
function appearance(key, label, description, themes) {
|
|
52
|
+
return { key, valueType: "enum", writable: true, choices: themes, label, description, resolvedWhenRead: true };
|
|
53
|
+
}
|
|
54
|
+
function offered(key) {
|
|
55
|
+
return PRESENTATION.settings[key]?.values ?? [];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The same, read as the numbers a numeric setting stores. Only for a setting
|
|
59
|
+
* whose list is its whole domain: where the engine offers a few numbers but
|
|
60
|
+
* accepts any within a range, the range governs and the list is only shortcuts.
|
|
61
|
+
*/
|
|
62
|
+
function offeredNumbers(key) {
|
|
63
|
+
return offered(key).map(value => Number.parseInt(value, 10)).filter(value => Number.isSafeInteger(value));
|
|
64
|
+
}
|
|
8
65
|
export class PiSettingsIntegration {
|
|
9
66
|
settings;
|
|
10
67
|
capabilities = { write: true, flush: true };
|
|
11
68
|
#operations;
|
|
12
|
-
|
|
69
|
+
#providers;
|
|
70
|
+
constructor(settings, providers = {}) {
|
|
13
71
|
this.settings = settings;
|
|
72
|
+
this.#providers = providers;
|
|
14
73
|
this.#operations = new Map(operations(settings).map(operation => [operation.descriptor.key, operation]));
|
|
15
74
|
if (this.#operations.size !== EXPOSED_SETTING_KEYS.length || EXPOSED_SETTING_KEYS.some(key => !this.#operations.has(key))) {
|
|
16
75
|
throw new Error("Pi settings integration does not cover every A1-exposed setting");
|
|
17
76
|
}
|
|
18
77
|
}
|
|
19
|
-
async listSettings() {
|
|
20
|
-
|
|
78
|
+
async listSettings() {
|
|
79
|
+
const rank = (key) => {
|
|
80
|
+
const at = PRESENTATION.order.indexOf(key);
|
|
81
|
+
return at < 0 ? PRESENTATION.order.length : at;
|
|
82
|
+
};
|
|
83
|
+
const listed = [...this.#operations.values()]
|
|
84
|
+
.map(operation => {
|
|
85
|
+
const key = operation.descriptor.key;
|
|
86
|
+
const wording = PRESENTATION.settings[key];
|
|
87
|
+
const flags = PRESENTATION.dialogs[key];
|
|
88
|
+
const bounds = PRESENTATION.bounds[key];
|
|
89
|
+
return {
|
|
90
|
+
...operation.descriptor,
|
|
91
|
+
...(bounds === undefined ? {} : bounds),
|
|
92
|
+
...(wording === undefined ? {} : { label: wording.label, description: wording.description }),
|
|
93
|
+
...(flags === undefined ? {} : { flags }),
|
|
94
|
+
};
|
|
95
|
+
})
|
|
96
|
+
.map(descriptor => this.#resolved(descriptor))
|
|
97
|
+
.sort((left, right) => rank(left.key) - rank(right.key));
|
|
98
|
+
const pair = parseAutomaticTheme(this.#storedTheme());
|
|
99
|
+
if (pair === null)
|
|
100
|
+
return listed;
|
|
101
|
+
const themes = this.#themes();
|
|
102
|
+
return [
|
|
103
|
+
...listed,
|
|
104
|
+
appearance(LIGHT_THEME_KEY, "Light theme", "Theme to use in automatic mode when the terminal is light", themes),
|
|
105
|
+
appearance(DARK_THEME_KEY, "Dark theme", "Theme to use in automatic mode when the terminal is dark", themes),
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
/** Overlays the values a setting can only offer once something is running. */
|
|
109
|
+
#resolved(descriptor) {
|
|
110
|
+
if (descriptor.key === THEME_KEY) {
|
|
111
|
+
const themes = this.#themes();
|
|
112
|
+
if (themes.length === 0)
|
|
113
|
+
return descriptor;
|
|
114
|
+
return { ...descriptor, valueType: "enum", writable: true, resolvedWhenRead: true, choices: [AUTOMATIC_THEME, ...themes] };
|
|
115
|
+
}
|
|
116
|
+
if (descriptor.key === THINKING_KEY) {
|
|
117
|
+
const levels = this.#providers.thinkingLevels?.() ?? [];
|
|
118
|
+
if (levels.length === 0)
|
|
119
|
+
return descriptor;
|
|
120
|
+
return { ...descriptor, resolvedWhenRead: true, choices: [...levels] };
|
|
121
|
+
}
|
|
122
|
+
return descriptor;
|
|
123
|
+
}
|
|
124
|
+
#themes() {
|
|
125
|
+
return this.#providers.themes?.() ?? [];
|
|
126
|
+
}
|
|
127
|
+
#storedTheme() {
|
|
128
|
+
const stored = this.#operations.get(THEME_KEY)?.read();
|
|
129
|
+
return typeof stored === "string" ? stored : "";
|
|
130
|
+
}
|
|
131
|
+
async readSetting(key) {
|
|
132
|
+
if (key === THEME_KEY || key === LIGHT_THEME_KEY || key === DARK_THEME_KEY) {
|
|
133
|
+
const pair = parseAutomaticTheme(this.#storedTheme());
|
|
134
|
+
if (key === THEME_KEY)
|
|
135
|
+
return pair === null ? this.#operations.get(key)?.read() : AUTOMATIC_THEME;
|
|
136
|
+
if (pair === null)
|
|
137
|
+
return undefined;
|
|
138
|
+
return key === LIGHT_THEME_KEY ? pair.light : pair.dark;
|
|
139
|
+
}
|
|
140
|
+
return this.#operations.get(key)?.read();
|
|
141
|
+
}
|
|
21
142
|
async writeSetting(key, value) { this.writeSettingNow(key, value); }
|
|
22
143
|
writeSettingNow(key, value) {
|
|
144
|
+
if (typeof value === "string" && (key === THEME_KEY || key === LIGHT_THEME_KEY || key === DARK_THEME_KEY)) {
|
|
145
|
+
this.#writeTheme(key, value);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
23
148
|
const operation = this.#operations.get(key);
|
|
24
149
|
if (!operation)
|
|
25
150
|
throw new Error(`setting is unavailable: ${key}`);
|
|
26
151
|
operation.write(value);
|
|
27
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Composes the stored theme from the part that changed. Turning automatic on
|
|
155
|
+
* starts from the theme already in use for both appearances, which is where
|
|
156
|
+
* the engine's own editor starts too.
|
|
157
|
+
*/
|
|
158
|
+
#writeTheme(key, value) {
|
|
159
|
+
const theme = this.#operations.get(THEME_KEY);
|
|
160
|
+
if (!theme)
|
|
161
|
+
throw new Error(`setting is unavailable: ${key}`);
|
|
162
|
+
const current = this.#storedTheme();
|
|
163
|
+
const pair = parseAutomaticTheme(current);
|
|
164
|
+
if (key === THEME_KEY) {
|
|
165
|
+
if (value !== AUTOMATIC_THEME)
|
|
166
|
+
theme.write(value);
|
|
167
|
+
else if (pair === null)
|
|
168
|
+
theme.write(`${current}/${current}`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (pair === null)
|
|
172
|
+
return;
|
|
173
|
+
theme.write(key === LIGHT_THEME_KEY ? `${value}/${pair.dark}` : `${pair.light}/${value}`);
|
|
174
|
+
}
|
|
28
175
|
async flush() { await this.settings.flush(); }
|
|
29
176
|
}
|
|
30
177
|
function operations(settings) {
|
|
@@ -35,30 +182,34 @@ function operations(settings) {
|
|
|
35
182
|
bool("autoResizeImages", () => settings.getImageAutoResize(), value => settings.setImageAutoResize(value)),
|
|
36
183
|
bool("blockImages", () => settings.getBlockImages(), value => settings.setBlockImages(value)),
|
|
37
184
|
bool("enableSkillCommands", () => settings.getEnableSkillCommands(), value => settings.setEnableSkillCommands(value)),
|
|
38
|
-
choice("steeringMode",
|
|
39
|
-
choice("followUpMode",
|
|
40
|
-
choice("transport",
|
|
185
|
+
choice("steeringMode", offered("steeringMode"), () => settings.getSteeringMode(), value => settings.setSteeringMode(value)),
|
|
186
|
+
choice("followUpMode", offered("followUpMode"), () => settings.getFollowUpMode(), value => settings.setFollowUpMode(value)),
|
|
187
|
+
choice("transport", offered("transport"), () => settings.getTransport(), value => settings.setTransport(value)),
|
|
41
188
|
numberSetting("httpIdleTimeoutMs", () => settings.getHttpIdleTimeoutMs(), value => settings.setHttpIdleTimeoutMs(value), 0),
|
|
189
|
+
// The engine reads these from the running session rather than declaring them,
|
|
190
|
+
// so they arrive through the runtime provider rather than from its source.
|
|
42
191
|
choice("thinkingLevel", ["off", "minimal", "low", "medium", "high", "xhigh"], () => settings.getDefaultThinkingLevel() ?? "medium", value => settings.setDefaultThinkingLevel(value)),
|
|
43
|
-
|
|
192
|
+
// The raw setting, not the resolved theme: the engine hides the automatic
|
|
193
|
+
// pair behind getTheme, and the pair is the thing A1 presents parts of.
|
|
194
|
+
stringSetting("theme", () => settings.getThemeSetting() ?? "default", value => settings.setTheme(value)),
|
|
44
195
|
bool("hideThinkingBlock", () => settings.getHideThinkingBlock(), value => settings.setHideThinkingBlock(value)),
|
|
45
|
-
choice("mermaidRenderingMode",
|
|
196
|
+
choice("mermaidRenderingMode", offered("mermaidRenderingMode"), () => settings.getMermaidRenderingMode(), value => settings.setMermaidRenderingMode(value)),
|
|
46
197
|
bool("showCacheMissNotices", () => settings.getShowCacheMissNotices(), value => settings.setShowCacheMissNotices(value)),
|
|
47
198
|
bool("collapseChangelog", () => settings.getCollapseChangelog(), value => settings.setCollapseChangelog(value)),
|
|
48
199
|
bool("enableInstallTelemetry", () => settings.getEnableInstallTelemetry(), value => settings.setEnableInstallTelemetry(value)),
|
|
49
200
|
bool("quietStartup", () => settings.getQuietStartup(), value => settings.setQuietStartup(value)),
|
|
50
|
-
choice("defaultProjectTrust",
|
|
51
|
-
choice("doubleEscapeAction",
|
|
52
|
-
choice("treeFilterMode",
|
|
201
|
+
choice("defaultProjectTrust", offered("defaultProjectTrust"), () => settings.getDefaultProjectTrust(), value => settings.setDefaultProjectTrust(value)),
|
|
202
|
+
choice("doubleEscapeAction", offered("doubleEscapeAction"), () => settings.getDoubleEscapeAction(), value => settings.setDoubleEscapeAction(value)),
|
|
203
|
+
choice("treeFilterMode", offered("treeFilterMode"), () => settings.getTreeFilterMode(), value => settings.setTreeFilterMode(value)),
|
|
53
204
|
bool("showHardwareCursor", () => settings.getShowHardwareCursor(), value => settings.setShowHardwareCursor(value)),
|
|
54
205
|
numberSetting("editorPaddingX", () => settings.getEditorPaddingX(), value => settings.setEditorPaddingX(value), 0),
|
|
55
|
-
choice("outputPad",
|
|
206
|
+
choice("outputPad", offeredNumbers("outputPad"), () => settings.getOutputPad(), value => settings.setOutputPad(value)),
|
|
56
207
|
numberSetting("autocompleteMaxVisible", () => settings.getAutocompleteMaxVisible(), value => settings.setAutocompleteMaxVisible(value), 1),
|
|
57
208
|
bool("clearOnShrink", () => settings.getClearOnShrink(), value => settings.setClearOnShrink(value)),
|
|
58
209
|
bool("showTerminalProgress", () => settings.getShowTerminalProgress(), value => settings.setShowTerminalProgress(value)),
|
|
59
|
-
choice("tuiMode",
|
|
60
|
-
choice("fullscreenExitOutput",
|
|
61
|
-
choice("fullscreenScrollbar",
|
|
210
|
+
choice("tuiMode", offered("tuiMode"), () => settings.getTuiMode(), value => settings.setTuiMode(value)),
|
|
211
|
+
choice("fullscreenExitOutput", offered("fullscreenExitOutput"), () => settings.getFullscreenExitOutput(), value => settings.setFullscreenExitOutput(value)),
|
|
212
|
+
choice("fullscreenScrollbar", offered("fullscreenScrollbar"), () => settings.getFullscreenScrollbar(), value => settings.setFullscreenScrollbar(value)),
|
|
62
213
|
jsonObject("warnings", () => settings.getWarnings(), value => settings.setWarnings(value)),
|
|
63
214
|
];
|
|
64
215
|
}
|
|
@@ -67,8 +218,18 @@ function bool(key, read, write) {
|
|
|
67
218
|
invalid(key); write(value); } };
|
|
68
219
|
}
|
|
69
220
|
function numberSetting(key, read, write, minimum) {
|
|
70
|
-
|
|
71
|
-
|
|
221
|
+
const declared = PRESENTATION.bounds[key];
|
|
222
|
+
const low = declared?.minimum ?? minimum;
|
|
223
|
+
const high = declared?.maximum;
|
|
224
|
+
return {
|
|
225
|
+
descriptor: { key, valueType: "number", writable: true },
|
|
226
|
+
read,
|
|
227
|
+
write(value) {
|
|
228
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < low || (high !== undefined && value > high))
|
|
229
|
+
invalid(key);
|
|
230
|
+
write(value);
|
|
231
|
+
},
|
|
232
|
+
};
|
|
72
233
|
}
|
|
73
234
|
function stringSetting(key, read, write) {
|
|
74
235
|
return { descriptor: { key, valueType: "string", writable: true }, read, write(value) { if (typeof value !== "string" || value.length === 0)
|
|
@@ -48,6 +48,15 @@ export interface PiWorkflowOption {
|
|
|
48
48
|
readonly label: string;
|
|
49
49
|
readonly description?: string;
|
|
50
50
|
}
|
|
51
|
+
export interface PiAuthenticationProviderStatus {
|
|
52
|
+
readonly type: "oauth" | "api_key";
|
|
53
|
+
readonly source?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface PiAuthenticationProviderOption extends PiWorkflowOption {
|
|
56
|
+
readonly providerId: string;
|
|
57
|
+
readonly authType: "oauth" | "api_key";
|
|
58
|
+
readonly status?: PiAuthenticationProviderStatus;
|
|
59
|
+
}
|
|
51
60
|
export interface PiWorkflowRequest {
|
|
52
61
|
readonly command: PiWorkflowRoute;
|
|
53
62
|
readonly argument: string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { PaneMouseEvent } from "../ui-components/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* The seam between the pinned shell and A1-owned screens. The shell asks whether
|
|
4
|
+
* a route is claimed, mounts whatever surface it is handed, and forwards keys to
|
|
5
|
+
* it — it never learns what an app is, and the app layer never reaches into the
|
|
6
|
+
* shell.
|
|
7
|
+
*/
|
|
8
|
+
export interface UiRouteSurface {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
/** Renders exactly `height` rows of at most `width` columns. */
|
|
11
|
+
render(width: number, height: number): readonly string[];
|
|
12
|
+
/** True when the key was consumed; false leaves it to the shell. */
|
|
13
|
+
handleInput(data: string): boolean;
|
|
14
|
+
/** Mouse report in surface-local coordinates. True when consumed. */
|
|
15
|
+
handleMouse(event: PaneMouseEvent): boolean;
|
|
16
|
+
/** True once the surface has asked to be dismissed. */
|
|
17
|
+
isClosed(): boolean;
|
|
18
|
+
close(): void;
|
|
19
|
+
/** Called by the shell when the surface should repaint. */
|
|
20
|
+
onRenderRequested(listener: () => void): void;
|
|
21
|
+
/** The surface asks to leave A1 entirely, not merely to close itself. */
|
|
22
|
+
onExitRequested(listener: () => void): void;
|
|
23
|
+
}
|
|
24
|
+
export interface UiRouteHost {
|
|
25
|
+
/** True when a declared A1-owned surface claims this route. */
|
|
26
|
+
claims(route: string): boolean;
|
|
27
|
+
/** Opens the route's surface, or null when it could not be presented. */
|
|
28
|
+
open(route: string): UiRouteSurface | null;
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OwnedUiSessionViewModel, OwnedUiThinkingLevel } from "../owned-ui-contracts/index.js";
|
|
2
|
+
import type { UiRouteHost } from "./route-host.js";
|
|
2
3
|
import { type AdapterCommandResult, type PiEngineAdapter, type PiWorkflowRequest, type PiWorkflowResult } from "../pi-engine-adapter/index.js";
|
|
3
4
|
import { type PiShellComponentPort, type PiShellEditorPort, type PiShellExtensionRendererResolver, type PiShellHeaderOptions, type PiShellHeaderPort, type PiShellLoadedResourcesPort, type PiShellSelectorOption, type PiShellTranscriptComponentPort } from "../pi-component-adapter/index.js";
|
|
4
5
|
import { PiTuiRuntimeAdapter, type PiTuiComponentPort, type PiTuiLayoutNode, type PiTuiOverlayHandle, type PiTuiTerminalPort } from "../pi-tui-runtime-adapter/index.js";
|
|
@@ -10,6 +11,11 @@ export interface OwnedUiSessionShellOptions {
|
|
|
10
11
|
readonly cwd: string;
|
|
11
12
|
readonly terminal?: OwnedUiTerminalPort;
|
|
12
13
|
readonly startup?: OwnedUiStartupOptions;
|
|
14
|
+
/**
|
|
15
|
+
* Declared A1-owned routes. A route this host claims resolves to its app;
|
|
16
|
+
* every other route continues to the pinned workflow table unchanged.
|
|
17
|
+
*/
|
|
18
|
+
readonly routeHost?: UiRouteHost;
|
|
13
19
|
}
|
|
14
20
|
export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
|
|
15
21
|
#private;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MOUSE_TRACKING_OFF, MOUSE_TRACKING_ON, parseMouseInput } from "../ui-components/index.js";
|
|
1
2
|
import { PINNED_PI_HIDDEN_COMMAND_NAMES, PINNED_PI_WORKFLOW_COMMAND_NAMES, } from "../pi-engine-adapter/index.js";
|
|
2
3
|
import { createPiExtensionUiBridge, createPiQueuedInputStatus, createPiShellArmin, createPiShellAuthProviderSelector, createPiShellChangelog, createPiShellDaxnuts, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellEditor, createPiShellExtensionSelector, createPiShellFooter, createPiShellHeader, createPiShellHotkeys, createPiShellLoadedResources, createPiShellLoginDialog, createPiShellModelSelector, createPiShellOperationLoader, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionInfo, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellStatus, createPiShellTranscriptComponent, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, piTheme, renderPiShellStatusText, renderPiShellTranscriptBlock, } from "../pi-component-adapter/index.js";
|
|
3
4
|
import { PiTuiRuntimeAdapter, } from "../pi-tui-runtime-adapter/index.js";
|
|
@@ -542,6 +543,7 @@ export class OwnedUiSessionShell {
|
|
|
542
543
|
#stopped;
|
|
543
544
|
#resolveStopped;
|
|
544
545
|
#dialogId;
|
|
546
|
+
#routeHost;
|
|
545
547
|
#dialogHandle;
|
|
546
548
|
#sequence = 0;
|
|
547
549
|
#started = false;
|
|
@@ -554,6 +556,7 @@ export class OwnedUiSessionShell {
|
|
|
554
556
|
this.backend = options.backend;
|
|
555
557
|
this.#sessionGeneration = this.backend.sessionGeneration;
|
|
556
558
|
this.#cwd = options.cwd;
|
|
559
|
+
this.#routeHost = options.routeHost ?? null;
|
|
557
560
|
this.#stopped = new Promise(resolve => {
|
|
558
561
|
this.#resolveStopped = resolve;
|
|
559
562
|
});
|
|
@@ -1243,6 +1246,69 @@ export class OwnedUiSessionShell {
|
|
|
1243
1246
|
for (const listener of this.#listeners)
|
|
1244
1247
|
listener(view);
|
|
1245
1248
|
}
|
|
1249
|
+
#openOwnedRoute(route) {
|
|
1250
|
+
const surface = this.#routeHost?.open(route) ?? null;
|
|
1251
|
+
if (surface === null)
|
|
1252
|
+
return { outcome: "failed", diagnostic: `route is unavailable: ${route}` };
|
|
1253
|
+
if (!this.runtime.active)
|
|
1254
|
+
return { outcome: "failed", diagnostic: "runtime is not active" };
|
|
1255
|
+
this.#dialogHandle?.hide();
|
|
1256
|
+
// Any-event reporting: hover and drag are what the screen is driven by, and
|
|
1257
|
+
// it also stops the terminal treating a drag as a text selection.
|
|
1258
|
+
this.runtime.writeControl(MOUSE_TRACKING_ON);
|
|
1259
|
+
// The interrupt chord is global, so it is watched on raw input rather than
|
|
1260
|
+
// through the overlay: the pinned shell handles that key before an overlay
|
|
1261
|
+
// ever sees it, which is why an owned screen must not rely on being asked.
|
|
1262
|
+
let armedAt = 0;
|
|
1263
|
+
const removeInterruptWatch = this.runtime.addInputListener(data => {
|
|
1264
|
+
if (!data.includes(INTERRUPT))
|
|
1265
|
+
return undefined;
|
|
1266
|
+
const now = Date.now();
|
|
1267
|
+
if (armedAt !== 0 && now - armedAt <= INTERRUPT_CHORD_MS) {
|
|
1268
|
+
armedAt = 0;
|
|
1269
|
+
closeSurface();
|
|
1270
|
+
void this.shutdown();
|
|
1271
|
+
return { consume: true };
|
|
1272
|
+
}
|
|
1273
|
+
armedAt = now;
|
|
1274
|
+
this.runtime.requestRender();
|
|
1275
|
+
// The presented screen owns the chord, so the pinned shell never sees a
|
|
1276
|
+
// stray interrupt while it is up.
|
|
1277
|
+
return { consume: true };
|
|
1278
|
+
});
|
|
1279
|
+
const closeSurface = () => {
|
|
1280
|
+
removeInterruptWatch();
|
|
1281
|
+
this.runtime.writeControl(MOUSE_TRACKING_OFF);
|
|
1282
|
+
this.#dialogHandle?.hide();
|
|
1283
|
+
this.#dialogHandle = undefined;
|
|
1284
|
+
this.#dialogId = undefined;
|
|
1285
|
+
};
|
|
1286
|
+
const rows = () => Math.max(1, this.runtime.viewport().rows);
|
|
1287
|
+
const component = {
|
|
1288
|
+
render: (width) => [...surface.render(Math.max(1, width), rows())],
|
|
1289
|
+
handleInput: (data) => {
|
|
1290
|
+
const { events, rest } = parseMouseInput(data);
|
|
1291
|
+
for (const event of events)
|
|
1292
|
+
surface.handleMouse(event);
|
|
1293
|
+
if (rest.length > 0)
|
|
1294
|
+
surface.handleInput(rest);
|
|
1295
|
+
if (surface.isClosed()) {
|
|
1296
|
+
closeSurface();
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
this.runtime.requestRender();
|
|
1300
|
+
},
|
|
1301
|
+
invalidate: () => this.runtime.requestRender(),
|
|
1302
|
+
};
|
|
1303
|
+
surface.onRenderRequested(() => this.runtime.requestRender());
|
|
1304
|
+
surface.onExitRequested(() => {
|
|
1305
|
+
closeSurface();
|
|
1306
|
+
void this.shutdown();
|
|
1307
|
+
});
|
|
1308
|
+
this.#dialogHandle = this.runtime.showOverlay(component, { width: "100%", maxHeight: "100%", anchor: "top-left" });
|
|
1309
|
+
this.#dialogId = surface.id;
|
|
1310
|
+
return { outcome: "completed", diagnostic: null };
|
|
1311
|
+
}
|
|
1246
1312
|
#syncDialog(dialog) {
|
|
1247
1313
|
if (!this.runtime.active)
|
|
1248
1314
|
return;
|
|
@@ -1273,6 +1339,8 @@ export class OwnedUiSessionShell {
|
|
|
1273
1339
|
const separator = body.search(/\s/);
|
|
1274
1340
|
const name = separator < 0 ? body : body.slice(0, separator);
|
|
1275
1341
|
const argument = separator < 0 ? "" : body.slice(separator + 1).trimStart();
|
|
1342
|
+
if (this.#routeHost?.claims(name))
|
|
1343
|
+
return this.#openOwnedRoute(name);
|
|
1276
1344
|
if (isWorkflowRoute(name))
|
|
1277
1345
|
return this.runWorkflow({ command: name, argument });
|
|
1278
1346
|
// Unknown slash input, prompt templates, skills, and extension commands remain Pi prompt input.
|
|
@@ -1362,7 +1430,7 @@ export class OwnedUiSessionShell {
|
|
|
1362
1430
|
? dialog.showManualInput(request.message)
|
|
1363
1431
|
: dialog.showPrompt(request.message, request.placeholder);
|
|
1364
1432
|
this.runtime.requestRender();
|
|
1365
|
-
return response.then(value => value
|
|
1433
|
+
return response.then(value => value, () => null);
|
|
1366
1434
|
}
|
|
1367
1435
|
#notifyWorkflowLogin(event) {
|
|
1368
1436
|
const dialog = this.#activeLoginDialog;
|
|
@@ -1430,6 +1498,8 @@ function workflowAdapterResult(result) {
|
|
|
1430
1498
|
return { outcome: "failed", diagnostic: result.message };
|
|
1431
1499
|
return { outcome: "rejected", diagnostic: result.message };
|
|
1432
1500
|
}
|
|
1501
|
+
const INTERRUPT = "\u0003";
|
|
1502
|
+
const INTERRUPT_CHORD_MS = 1_500;
|
|
1433
1503
|
function isWorkflowRoute(value) {
|
|
1434
1504
|
return PINNED_PI_WORKFLOW_COMMAND_NAMES.includes(value)
|
|
1435
1505
|
|| PINNED_PI_HIDDEN_COMMAND_NAMES.includes(value);
|
|
@@ -20,6 +20,12 @@ export declare class PiTuiRuntimeAdapter {
|
|
|
20
20
|
setFocus(component: PiTuiComponentPort | null): void;
|
|
21
21
|
showOverlay(component: PiTuiComponentPort, options?: PiTuiOverlayOptions): PiTuiOverlayHandle;
|
|
22
22
|
hasOverlay(): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Writes a terminal control sequence. Used to enable and disable mouse
|
|
25
|
+
* reporting while an A1-owned application is presented, and for nothing else:
|
|
26
|
+
* the transparent and pinned paths never call it.
|
|
27
|
+
*/
|
|
28
|
+
writeControl(data: string): void;
|
|
23
29
|
addInputListener(listener: PiTuiInputListener): () => void;
|
|
24
30
|
switchMode(mode: "regular" | "fullscreen"): boolean;
|
|
25
31
|
setHardwareCursor(enabled: boolean): void;
|
|
@@ -230,6 +230,15 @@ export class PiTuiRuntimeAdapter {
|
|
|
230
230
|
hasOverlay() {
|
|
231
231
|
return this.#tui.hasOverlay();
|
|
232
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Writes a terminal control sequence. Used to enable and disable mouse
|
|
235
|
+
* reporting while an A1-owned application is presented, and for nothing else:
|
|
236
|
+
* the transparent and pinned paths never call it.
|
|
237
|
+
*/
|
|
238
|
+
writeControl(data) {
|
|
239
|
+
this.#assertRunning("control sequence");
|
|
240
|
+
this.#terminal.write(data);
|
|
241
|
+
}
|
|
233
242
|
addInputListener(listener) {
|
|
234
243
|
this.#assertRunning("input listener");
|
|
235
244
|
if (this.#inputListeners.has(listener))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function verifyProcessGuardianArtifact(helperPath: string, platform?: NodeJS.Platform, architecture?: string): Promise<void>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, resolve } from "node:path";
|
|
4
|
+
export async function verifyProcessGuardianArtifact(helperPath, platform = process.platform, architecture = process.arch) {
|
|
5
|
+
const manifestPath = resolve(dirname(helperPath), "manifest.json");
|
|
6
|
+
let manifest;
|
|
7
|
+
try {
|
|
8
|
+
manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
throw artifactError(`process guardian manifest is missing or malformed: ${errorMessage(error)}`, "CONTAINMENT_ARTIFACT_MISSING");
|
|
12
|
+
}
|
|
13
|
+
if (manifest.schema !== "a1-process-guardian-artifact-v1" || manifest.protocolVersion !== 1
|
|
14
|
+
|| manifest.platform !== platform || manifest.architecture !== architecture
|
|
15
|
+
|| manifest.artifact?.filename !== basename(helperPath)) {
|
|
16
|
+
throw artifactError("process guardian manifest is incompatible with this platform or protocol", "CONTAINMENT_ARTIFACT_INCOMPATIBLE");
|
|
17
|
+
}
|
|
18
|
+
if (manifest.capability !== "supported") {
|
|
19
|
+
throw artifactError(`process containment is not certified for ${platform}-${architecture}`, "CONTAINMENT_UNSUPPORTED");
|
|
20
|
+
}
|
|
21
|
+
const [bytes, metadata] = await Promise.all([readFile(helperPath), stat(helperPath)]).catch(error => {
|
|
22
|
+
throw artifactError(`process guardian artifact is unavailable: ${errorMessage(error)}`, "CONTAINMENT_ARTIFACT_MISSING");
|
|
23
|
+
});
|
|
24
|
+
const digest = createHash("sha256").update(bytes).digest("hex");
|
|
25
|
+
if (metadata.size !== manifest.artifact.size || digest !== manifest.artifact.sha256) {
|
|
26
|
+
throw artifactError("process guardian artifact integrity verification failed", "CONTAINMENT_ARTIFACT_TAMPERED");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function artifactError(message, code) {
|
|
30
|
+
return Object.assign(new Error(message), { code });
|
|
31
|
+
}
|
|
32
|
+
function errorMessage(error) {
|
|
33
|
+
return error instanceof Error ? error.message : String(error);
|
|
34
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { LaunchInstanceOutcome, LaunchInstanceStopReason, NativeProcessIdentity, ProcessContainmentIdentity } from "../lifecycle/index.js";
|
|
2
|
+
export interface ContainedSpawnOptions {
|
|
3
|
+
readonly cwd: string;
|
|
4
|
+
readonly environment: Readonly<Record<string, string>>;
|
|
5
|
+
readonly terminalType?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ContainedProcessHandle {
|
|
8
|
+
readonly identity: NativeProcessIdentity;
|
|
9
|
+
readonly outcome: Promise<LaunchInstanceOutcome>;
|
|
10
|
+
}
|
|
11
|
+
export interface NativeProcessInspector {
|
|
12
|
+
observe(pid: number): Promise<NativeProcessIdentity | null>;
|
|
13
|
+
matches(identity: NativeProcessIdentity): Promise<boolean>;
|
|
14
|
+
}
|
|
15
|
+
export interface ProcessContainment {
|
|
16
|
+
readonly identity: ProcessContainmentIdentity;
|
|
17
|
+
spawn(executable: string, arguments_: readonly string[], options: ContainedSpawnOptions): Promise<ContainedProcessHandle>;
|
|
18
|
+
contains(identity: NativeProcessIdentity): Promise<boolean>;
|
|
19
|
+
stop(force: boolean): Promise<void>;
|
|
20
|
+
waitForEmpty(timeoutMs: number): Promise<boolean>;
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export interface ProcessContainmentFactory {
|
|
24
|
+
create(instanceId: string): Promise<ProcessContainment>;
|
|
25
|
+
}
|
|
26
|
+
export interface ContainmentCloseResult {
|
|
27
|
+
readonly outcome: LaunchInstanceOutcome;
|
|
28
|
+
readonly graceful: boolean;
|
|
29
|
+
readonly forced: boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare function closeVerifiedContainment(containment: ProcessContainment, inspector: NativeProcessInspector, rootIdentity: NativeProcessIdentity, reason: LaunchInstanceStopReason, deadlineMs?: number): Promise<ContainmentCloseResult>;
|