@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.
Files changed (151) hide show
  1. package/README.md +32 -9
  2. package/bin/a1-guardian.js +22 -0
  3. package/bin/a1-ui.js +7 -4
  4. package/dist/native/darwin-arm64/a1-process-guardian +0 -0
  5. package/dist/native/darwin-arm64/manifest.json +20 -0
  6. package/dist/native/linux-x64/a1-process-guardian +0 -0
  7. package/dist/native/linux-x64/manifest.json +20 -0
  8. package/dist/native/win32-x64/a1-process-guardian.exe +0 -0
  9. package/dist/native/win32-x64/manifest.json +20 -0
  10. package/dist/src/composition/index.d.ts +26 -0
  11. package/dist/src/composition/index.js +88 -2
  12. package/dist/src/features/launch/runtime-selection.d.ts +2 -1
  13. package/dist/src/features/launch/runtime-selection.js +2 -2
  14. package/dist/src/features/owned-ui/index.d.ts +1 -0
  15. package/dist/src/features/owned-ui/index.js +1 -0
  16. package/dist/src/features/owned-ui/run.d.ts +6 -0
  17. package/dist/src/features/owned-ui/run.js +3 -1
  18. package/dist/src/features/owned-ui/settings-app.d.ts +22 -0
  19. package/dist/src/features/owned-ui/settings-app.js +631 -0
  20. package/dist/src/foundation/agent-engine-contracts/domain.d.ts +26 -0
  21. package/dist/src/foundation/launch-guardian/index.d.ts +1 -0
  22. package/dist/src/foundation/launch-guardian/index.js +1 -0
  23. package/dist/src/foundation/launch-guardian/main.d.ts +31 -0
  24. package/dist/src/foundation/launch-guardian/main.js +165 -0
  25. package/dist/src/foundation/lifecycle/commands.d.ts +40 -0
  26. package/dist/src/foundation/lifecycle/commands.js +1 -0
  27. package/dist/src/foundation/lifecycle/index.d.ts +2 -0
  28. package/dist/src/foundation/lifecycle/index.js +2 -0
  29. package/dist/src/foundation/lifecycle/launch-instance.d.ts +46 -0
  30. package/dist/src/foundation/lifecycle/launch-instance.js +134 -0
  31. package/dist/src/foundation/lifecycle/model.d.ts +6 -42
  32. package/dist/src/foundation/owned-ui-settings/declarations.d.ts +13 -0
  33. package/dist/src/foundation/owned-ui-settings/declarations.js +39 -0
  34. package/dist/src/foundation/owned-ui-settings/index.d.ts +6 -0
  35. package/dist/src/foundation/owned-ui-settings/index.js +6 -0
  36. package/dist/src/foundation/owned-ui-settings/migrations.d.ts +13 -0
  37. package/dist/src/foundation/owned-ui-settings/migrations.js +28 -0
  38. package/dist/src/foundation/owned-ui-settings/resolution.d.ts +45 -0
  39. package/dist/src/foundation/owned-ui-settings/resolution.js +147 -0
  40. package/dist/src/foundation/owned-ui-settings/sections.d.ts +70 -0
  41. package/dist/src/foundation/owned-ui-settings/sections.js +93 -0
  42. package/dist/src/foundation/owned-ui-settings/session.d.ts +44 -0
  43. package/dist/src/foundation/owned-ui-settings/session.js +145 -0
  44. package/dist/src/foundation/owned-ui-settings/store.d.ts +28 -0
  45. package/dist/src/foundation/owned-ui-settings/store.js +90 -0
  46. package/dist/src/foundation/pi-component-adapter/index.d.ts +1 -0
  47. package/dist/src/foundation/pi-component-adapter/index.js +1 -0
  48. package/dist/src/foundation/pi-component-adapter/shell-selectors-dialogs.d.ts +9 -1
  49. package/dist/src/foundation/pi-component-adapter/shell-selectors-dialogs.js +10 -8
  50. package/dist/src/foundation/pi-engine-adapter/adapter.d.ts +12 -3
  51. package/dist/src/foundation/pi-engine-adapter/adapter.js +73 -7
  52. package/dist/src/foundation/pi-engine-adapter/pi-settings-metadata.json +374 -0
  53. package/dist/src/foundation/pi-engine-adapter/public-main-entry.js +10 -1
  54. package/dist/src/foundation/pi-engine-adapter/settings-integration.d.ts +42 -2
  55. package/dist/src/foundation/pi-engine-adapter/settings-integration.js +185 -24
  56. package/dist/src/foundation/pi-engine-adapter/workflows.d.ts +9 -0
  57. package/dist/src/foundation/pi-owned-ui-integration/index.d.ts +1 -0
  58. package/dist/src/foundation/pi-owned-ui-integration/index.js +1 -0
  59. package/dist/src/foundation/pi-owned-ui-integration/route-host.d.ts +29 -0
  60. package/dist/src/foundation/pi-owned-ui-integration/route-host.js +1 -0
  61. package/dist/src/foundation/pi-owned-ui-integration/session-shell.d.ts +6 -0
  62. package/dist/src/foundation/pi-owned-ui-integration/session-shell.js +71 -1
  63. package/dist/src/foundation/pi-tui-runtime-adapter/adapter.d.ts +6 -0
  64. package/dist/src/foundation/pi-tui-runtime-adapter/adapter.js +9 -0
  65. package/dist/src/foundation/process-containment/artifact.d.ts +1 -0
  66. package/dist/src/foundation/process-containment/artifact.js +34 -0
  67. package/dist/src/foundation/process-containment/contracts.d.ts +31 -0
  68. package/dist/src/foundation/process-containment/contracts.js +35 -0
  69. package/dist/src/foundation/process-containment/index.d.ts +5 -0
  70. package/dist/src/foundation/process-containment/index.js +5 -0
  71. package/dist/src/foundation/process-containment/linux-process-inspector.d.ts +6 -0
  72. package/dist/src/foundation/process-containment/linux-process-inspector.js +26 -0
  73. package/dist/src/foundation/process-containment/native-guardian-containment.d.ts +16 -0
  74. package/dist/src/foundation/process-containment/native-guardian-containment.js +139 -0
  75. package/dist/src/foundation/process-containment/windows-process-inspector.d.ts +17 -0
  76. package/dist/src/foundation/process-containment/windows-process-inspector.js +53 -0
  77. package/dist/src/foundation/protocol/client.d.ts +2 -1
  78. package/dist/src/foundation/protocol/client.js +3 -0
  79. package/dist/src/foundation/protocol/messages.d.ts +5 -5
  80. package/dist/src/foundation/protocol/messages.js +1 -1
  81. package/dist/src/foundation/release/bootstrap.js +29 -10
  82. package/dist/src/foundation/release/cohort-selection.js +3 -3
  83. package/dist/src/foundation/release/cohort-state.d.ts +3 -2
  84. package/dist/src/foundation/release/process-cleanup.d.ts +1 -1
  85. package/dist/src/foundation/release/process-cleanup.js +2 -2
  86. package/dist/src/foundation/release/update.js +2 -2
  87. package/dist/src/foundation/storage/control-store.d.ts +8 -7
  88. package/dist/src/foundation/storage/control-store.js +105 -55
  89. package/dist/src/foundation/supervision/server.d.ts +4 -2
  90. package/dist/src/foundation/supervision/server.js +254 -87
  91. package/dist/src/foundation/transparent-terminal/command-resolution.js +6 -3
  92. package/dist/src/foundation/transparent-terminal/foreground-broker.d.ts +5 -13
  93. package/dist/src/foundation/transparent-terminal/foreground-broker.js +11 -68
  94. package/dist/src/foundation/transparent-terminal/main.js +1 -8
  95. package/dist/src/foundation/ui-apps/contracts.d.ts +42 -0
  96. package/dist/src/foundation/ui-apps/contracts.js +12 -0
  97. package/dist/src/foundation/ui-apps/host.d.ts +42 -0
  98. package/dist/src/foundation/ui-apps/host.js +152 -0
  99. package/dist/src/foundation/ui-apps/index.d.ts +3 -0
  100. package/dist/src/foundation/ui-apps/index.js +3 -0
  101. package/dist/src/foundation/ui-apps/registry.d.ts +11 -0
  102. package/dist/src/foundation/ui-apps/registry.js +32 -0
  103. package/dist/src/foundation/ui-components/dialog-panel.d.ts +36 -0
  104. package/dist/src/foundation/ui-components/dialog-panel.js +47 -0
  105. package/dist/src/foundation/ui-components/frame.d.ts +21 -0
  106. package/dist/src/foundation/ui-components/frame.js +54 -0
  107. package/dist/src/foundation/ui-components/index.d.ts +18 -0
  108. package/dist/src/foundation/ui-components/index.js +18 -0
  109. package/dist/src/foundation/ui-components/label.d.ts +8 -0
  110. package/dist/src/foundation/ui-components/label.js +36 -0
  111. package/dist/src/foundation/ui-components/line-input.d.ts +74 -0
  112. package/dist/src/foundation/ui-components/line-input.js +245 -0
  113. package/dist/src/foundation/ui-components/list-block.d.ts +66 -0
  114. package/dist/src/foundation/ui-components/list-block.js +176 -0
  115. package/dist/src/foundation/ui-components/list-view.d.ts +66 -0
  116. package/dist/src/foundation/ui-components/list-view.js +76 -0
  117. package/dist/src/foundation/ui-components/mouse.d.ts +15 -0
  118. package/dist/src/foundation/ui-components/mouse.js +46 -0
  119. package/dist/src/foundation/ui-components/pane.d.ts +33 -0
  120. package/dist/src/foundation/ui-components/pane.js +7 -0
  121. package/dist/src/foundation/ui-components/revision.d.ts +35 -0
  122. package/dist/src/foundation/ui-components/revision.js +79 -0
  123. package/dist/src/foundation/ui-components/scrollbar.d.ts +62 -0
  124. package/dist/src/foundation/ui-components/scrollbar.js +102 -0
  125. package/dist/src/foundation/ui-components/shortcuts.d.ts +57 -0
  126. package/dist/src/foundation/ui-components/shortcuts.js +81 -0
  127. package/dist/src/foundation/ui-components/spans.d.ts +7 -0
  128. package/dist/src/foundation/ui-components/spans.js +59 -0
  129. package/dist/src/foundation/ui-components/status-line.d.ts +16 -0
  130. package/dist/src/foundation/ui-components/status-line.js +14 -0
  131. package/dist/src/foundation/ui-components/stepper.d.ts +24 -0
  132. package/dist/src/foundation/ui-components/stepper.js +33 -0
  133. package/dist/src/foundation/ui-components/surface.d.ts +20 -0
  134. package/dist/src/foundation/ui-components/surface.js +38 -0
  135. package/dist/src/foundation/ui-components/text.d.ts +24 -0
  136. package/dist/src/foundation/ui-components/text.js +123 -0
  137. package/dist/src/foundation/ui-components/theme.d.ts +17 -0
  138. package/dist/src/foundation/ui-components/theme.js +12 -0
  139. package/dist/src/foundation/ui-components/value-menu.d.ts +41 -0
  140. package/dist/src/foundation/ui-components/value-menu.js +33 -0
  141. package/dist/src/product-identity.d.ts +2 -2
  142. package/dist/src/product-identity.js +2 -1
  143. package/dist/src/product-identity.json +3 -0
  144. package/docs/architecture/boundaries.md +31 -13
  145. package/docs/architecture/process-guardian-provenance.md +26 -0
  146. package/docs/architecture/toolchain.md +6 -0
  147. package/docs/architecture/ui-reference-provenance.md +50 -0
  148. package/docs/ci-release-runbook.md +38 -42
  149. package/docs/features/launch-profiles.md +7 -1
  150. package/docs/manual-launch-instance-acceptance.md +66 -0
  151. 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
- export declare const EXPOSED_SETTING_KEYS: readonly ["autoCompact", "showImages", "imageWidthCells", "autoResizeImages", "blockImages", "enableSkillCommands", "steeringMode", "followUpMode", "transport", "httpIdleTimeoutMs", "thinkingLevel", "theme", "hideThinkingBlock", "mermaidRenderingMode", "showCacheMissNotices", "collapseChangelog", "enableInstallTelemetry", "quietStartup", "defaultProjectTrust", "doubleEscapeAction", "treeFilterMode", "showHardwareCursor", "editorPaddingX", "outputPad", "autocompleteMaxVisible", "clearOnShrink", "showTerminalProgress", "tuiMode", "fullscreenExitOutput", "fullscreenScrollbar", "warnings"];
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
- export const EXPOSED_SETTING_KEYS = Object.freeze([
2
- "autoCompact", "showImages", "imageWidthCells", "autoResizeImages", "blockImages", "enableSkillCommands",
3
- "steeringMode", "followUpMode", "transport", "httpIdleTimeoutMs", "thinkingLevel", "theme", "hideThinkingBlock",
4
- "mermaidRenderingMode", "showCacheMissNotices", "collapseChangelog", "enableInstallTelemetry", "quietStartup",
5
- "defaultProjectTrust", "doubleEscapeAction", "treeFilterMode", "showHardwareCursor", "editorPaddingX", "outputPad",
6
- "autocompleteMaxVisible", "clearOnShrink", "showTerminalProgress", "tuiMode", "fullscreenExitOutput", "fullscreenScrollbar", "warnings",
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
- constructor(settings) {
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() { return [...this.#operations.values()].map(operation => operation.descriptor); }
20
- async readSetting(key) { return this.#operations.get(key)?.read(); }
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", ["all", "one-at-a-time"], () => settings.getSteeringMode(), value => settings.setSteeringMode(value)),
39
- choice("followUpMode", ["all", "one-at-a-time"], () => settings.getFollowUpMode(), value => settings.setFollowUpMode(value)),
40
- choice("transport", ["sse", "websocket", "websocket-cached", "auto"], () => settings.getTransport(), value => settings.setTransport(value)),
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
- stringSetting("theme", () => settings.getTheme() ?? "default", value => settings.setTheme(value)),
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", ["off", "final", "streaming"], () => settings.getMermaidRenderingMode(), value => settings.setMermaidRenderingMode(value)),
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", ["ask", "always", "never"], () => settings.getDefaultProjectTrust(), value => settings.setDefaultProjectTrust(value)),
51
- choice("doubleEscapeAction", ["fork", "tree", "none"], () => settings.getDoubleEscapeAction(), value => settings.setDoubleEscapeAction(value)),
52
- choice("treeFilterMode", ["default", "no-tools", "user-only", "labeled-only", "all"], () => settings.getTreeFilterMode(), value => settings.setTreeFilterMode(value)),
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", [0, 1], () => settings.getOutputPad(), value => settings.setOutputPad(value)),
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", ["regular", "fullscreen"], () => settings.getTuiMode(), value => settings.setTuiMode(value)),
60
- choice("fullscreenExitOutput", ["transcript", "resume-hint"], () => settings.getFullscreenExitOutput(), value => settings.setFullscreenExitOutput(value)),
61
- choice("fullscreenScrollbar", ["hidden", "auto", "always"], () => settings.getFullscreenScrollbar(), value => settings.setFullscreenScrollbar(value)),
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
- return { descriptor: { key, valueType: "number", writable: true }, read, write(value) { if (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum)
71
- invalid(key); write(value); } };
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;
@@ -1 +1,2 @@
1
1
  export * from "./session-shell.js";
2
+ export * from "./route-host.js";
@@ -1 +1,2 @@
1
1
  export * from "./session-shell.js";
2
+ export * from "./route-host.js";
@@ -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
+ }
@@ -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.trim() || null, () => null);
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>;