@timurproko/a1 0.1.8-dev.137 → 0.1.8-dev.151

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 (105) hide show
  1. package/README.md +2 -4
  2. package/dist/cli/capabilities.d.ts +2 -11
  3. package/dist/cli/capabilities.js +1 -1
  4. package/dist/cli/dispatch.js +3 -6
  5. package/dist/composition/owned-ui.js +26 -5
  6. package/dist/composition/settings-route-host.d.ts +1 -1
  7. package/dist/composition/settings-route-host.js +1 -1
  8. package/dist/contracts/owned-ui/model.d.ts +28 -0
  9. package/dist/contracts/owned-ui/validation.js +14 -0
  10. package/dist/features/launch/development-launch.js +11 -3
  11. package/dist/features/launch/index.d.ts +0 -1
  12. package/dist/features/launch/index.js +0 -1
  13. package/dist/features/launch/intent.d.ts +3 -2
  14. package/dist/features/launch/intent.js +1 -2
  15. package/dist/features/launch/prepare-launch.d.ts +0 -2
  16. package/dist/features/launch/prepare-launch.js +2 -9
  17. package/dist/features/launch/profile-paths.d.ts +1 -2
  18. package/dist/features/launch/profile-paths.js +2 -8
  19. package/dist/features/launch/runtime-selection.d.ts +2 -5
  20. package/dist/features/owned-ui/settings-app.js +38 -14
  21. package/dist/foundation/lifecycle/model.d.ts +1 -1
  22. package/dist/foundation/lifecycle/model.js +1 -1
  23. package/dist/foundation/release/bootstrap.d.ts +1 -3
  24. package/dist/foundation/release/bootstrap.js +1 -1
  25. package/dist/foundation/storage/control-store.js +37 -3
  26. package/dist/integrations/pi/components/owned-editor-ux.d.ts +55 -0
  27. package/dist/integrations/pi/components/owned-editor-ux.js +778 -0
  28. package/dist/integrations/pi/components/shell-editor-autocomplete.js +44 -6
  29. package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
  30. package/dist/integrations/pi/components/shell-footer-status.js +14 -5
  31. package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -2
  32. package/dist/integrations/pi/components/shell-presenters-transcript.js +9 -8
  33. package/dist/integrations/pi/components/shell-shared-facade.d.ts +41 -7
  34. package/dist/integrations/pi/components/shell-shared-facade.js +3 -1
  35. package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +13 -0
  36. package/dist/integrations/pi/components/submitted-prompt-adapter.js +27 -0
  37. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +10 -2
  38. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +36 -3
  39. package/dist/integrations/pi/engine/adapter.js +21 -3
  40. package/dist/integrations/pi/engine/session-integration.d.ts +2 -1
  41. package/dist/integrations/pi/engine/session-integration.js +17 -3
  42. package/dist/integrations/pi/session-ui/prompt-chips.d.ts +23 -0
  43. package/dist/integrations/pi/session-ui/prompt-chips.js +242 -0
  44. package/dist/integrations/pi/session-ui/route-host.d.ts +5 -0
  45. package/dist/integrations/pi/{owned-ui → session-ui}/session-shell-root.d.ts +34 -5
  46. package/dist/integrations/pi/{owned-ui → session-ui}/session-shell-root.js +294 -45
  47. package/dist/integrations/pi/{owned-ui → session-ui}/session-shell.d.ts +1 -1
  48. package/dist/integrations/pi/{owned-ui → session-ui}/session-shell.js +167 -24
  49. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +39 -0
  50. package/dist/integrations/pi/session-ui/session-viewport-controller.js +369 -0
  51. package/dist/integrations/pi/session-ui/system-clipboard.d.ts +11 -0
  52. package/dist/integrations/pi/session-ui/system-clipboard.js +101 -0
  53. package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -1
  54. package/dist/integrations/pi/tui-runtime/adapter.js +51 -2
  55. package/dist/integrations/pi/tui-runtime/contracts.d.ts +4 -6
  56. package/dist/native/darwin-arm64/manifest.json +1 -1
  57. package/dist/native/linux-x64/manifest.json +1 -1
  58. package/dist/native/win32-x64/manifest.json +3 -3
  59. package/dist/native/win32-x64/process-guardian.exe +0 -0
  60. package/dist/product-identity.d.ts +2 -2
  61. package/dist/product-identity.js +3 -4
  62. package/dist/product-identity.json +3 -5
  63. package/dist/ui/apps/contracts.d.ts +23 -1
  64. package/dist/ui/apps/host.js +9 -1
  65. package/dist/ui/components/index.d.ts +3 -0
  66. package/dist/ui/components/index.js +3 -0
  67. package/dist/ui/components/mouse.d.ts +10 -0
  68. package/dist/ui/components/mouse.js +23 -0
  69. package/dist/ui/components/prompt-row.d.ts +14 -0
  70. package/dist/ui/components/prompt-row.js +20 -0
  71. package/dist/ui/components/scrollbar.d.ts +26 -0
  72. package/dist/ui/components/scrollbar.js +35 -0
  73. package/dist/ui/components/spans.d.ts +21 -0
  74. package/dist/ui/components/spans.js +178 -0
  75. package/dist/ui/components/submitted-prompt.d.ts +23 -0
  76. package/dist/ui/components/submitted-prompt.js +50 -0
  77. package/dist/ui/components/text-selection.d.ts +53 -0
  78. package/dist/ui/components/text-selection.js +168 -0
  79. package/dist/ui/components/text.d.ts +14 -0
  80. package/dist/ui/components/text.js +40 -0
  81. package/dist/ui/components/transcript-viewport.d.ts +104 -0
  82. package/dist/ui/components/transcript-viewport.js +381 -0
  83. package/dist/ui/settings/declarations.d.ts +8 -1
  84. package/dist/ui/settings/declarations.js +33 -5
  85. package/dist/ui/settings/migrations.d.ts +1 -1
  86. package/dist/ui/settings/migrations.js +21 -2
  87. package/dist/ui/settings/sections.js +15 -8
  88. package/dist/ui/settings/session.d.ts +2 -0
  89. package/dist/ui/settings/session.js +7 -5
  90. package/docs/architecture/boundaries.md +3 -3
  91. package/docs/architecture/project-structure.md +2 -2
  92. package/docs/architecture/resource-and-data-policy.md +1 -1
  93. package/docs/architecture/toolchain.md +5 -5
  94. package/docs/architecture/ui-reference-provenance.md +11 -2
  95. package/docs/features/launch-profiles.md +7 -14
  96. package/docs/manual-launch-instance-acceptance.md +12 -35
  97. package/docs/manual-owned-ui-checkpoint.md +2 -3
  98. package/docs/manual-pi-boundary-candidate.md +4 -5
  99. package/package.json +4 -2
  100. package/dist/features/launch/profiles.d.ts +0 -12
  101. package/dist/features/launch/profiles.js +0 -26
  102. package/dist/integrations/pi/owned-ui/route-host.d.ts +0 -29
  103. /package/dist/integrations/pi/{owned-ui → session-ui}/index.d.ts +0 -0
  104. /package/dist/integrations/pi/{owned-ui → session-ui}/index.js +0 -0
  105. /package/dist/integrations/pi/{owned-ui → session-ui}/route-host.js +0 -0
package/README.md CHANGED
@@ -31,17 +31,16 @@ a1 update:107 # install numbered preview 107
31
31
  a1 update --models # refresh A1's model catalogs
32
32
  ```
33
33
 
34
- Development previews add two extra profiles; release builds do not carry them.
34
+ Development previews add the Pi comparison profile; release builds do not carry it.
35
35
 
36
36
  ```sh
37
37
  a1 pi # vanilla Pi oracle: ~/.pi/agent
38
- a1 sandbox # isolated vanilla Pi profile: ~/.a1/sandbox
39
38
  ```
40
39
 
41
40
  ## Extensions
42
41
 
43
42
  Pi extension packages install into A1's own profile (`~/.a1/agent`), so bare `a1`
44
- loads them and `a1 pi` / `a1 sandbox` do not. Sources are Pi's: `npm:`, git, or a
43
+ loads them and `a1 pi` does not. Sources are Pi's: `npm:`, git, or a
45
44
  local path.
46
45
 
47
46
  ```sh
@@ -63,7 +62,6 @@ npm ci # install exact locked dependencies
63
62
  npm run build # compile TypeScript and the process guardian into dist
64
63
  npm start # build and launch a development `a1`
65
64
  npm run start:pi # build and launch a development `a1 pi`
66
- npm run start:sandbox # build and launch a development `a1 sandbox`
67
65
  npm run test:fast # typecheck + fast suite (alias: npm test)
68
66
  npm run test:full # complete non-physical suite
69
67
  ```
@@ -1,15 +1,6 @@
1
- /**
2
- * `pi` and `sandbox` exist to compare A1 against pinned Pi and to try resources
3
- * against an isolated profile. Both are development instruments rather than
4
- * product, so a stable release does not carry them: what a released `a1` exposes
5
- * is the product plus its maintenance and package commands.
6
- *
7
- * Which build this is comes from its own version. A prerelease version is a
8
- * `next`-channel build, which is where that work happens; a release version is
9
- * not. Nothing to configure, and no way for a released build to be talked into it.
10
- */
1
+ /** The Pi comparison launch is a prerelease development instrument, not product. */
11
2
  export interface CliCapabilities {
12
- readonly developmentProfiles: boolean;
3
+ readonly developmentComparison: boolean;
13
4
  }
14
5
  export declare function cliCapabilities(version: string): CliCapabilities;
15
6
  export declare function isPrereleaseVersion(version: string): boolean;
@@ -1,6 +1,6 @@
1
1
  import { prerelease } from "semver";
2
2
  export function cliCapabilities(version) {
3
- return Object.freeze({ developmentProfiles: isPrereleaseVersion(version) });
3
+ return Object.freeze({ developmentComparison: isPrereleaseVersion(version) });
4
4
  }
5
5
  export function isPrereleaseVersion(version) {
6
6
  return (prerelease(version)?.length ?? 0) > 0;
@@ -3,7 +3,7 @@ import { PRODUCT_TEXT } from "../product-identity.js";
3
3
  export function cliUsage(capabilities) {
4
4
  return PRODUCT_TEXT.usage([
5
5
  "",
6
- ...(capabilities.developmentProfiles ? ["pi", "sandbox"] : []),
6
+ ...(capabilities.developmentComparison ? ["pi"] : []),
7
7
  "version",
8
8
  "update [self|--models]",
9
9
  "update:develop",
@@ -14,7 +14,7 @@ export function cliUsage(capabilities) {
14
14
  "pi update [--extensions|<source>]",
15
15
  ]);
16
16
  }
17
- const PROFILE_WORDS = new Set(["pi", "sandbox"]);
17
+ const PROFILE_WORDS = new Set(["pi"]);
18
18
  export async function dispatchCli(arguments_, handlers, output, capabilities) {
19
19
  const command = parseCliCommand(arguments_, capabilities);
20
20
  if (command.kind === "error") {
@@ -36,12 +36,9 @@ export function parseCliCommand(arguments_, capabilities) {
36
36
  if (command === "pi") {
37
37
  if (rest.length > 0)
38
38
  return parsePiPackageCommand(rest);
39
- if (capabilities.developmentProfiles)
39
+ if (capabilities.developmentComparison)
40
40
  return { kind: "launch", profileId: "pi" };
41
41
  }
42
- if (capabilities.developmentProfiles && command === "sandbox") {
43
- return withoutArguments(rest, { kind: "launch", profileId: command });
44
- }
45
42
  if (command === "version")
46
43
  return withoutArguments(rest, { kind: "version" });
47
44
  if (command !== undefined && command.startsWith("update:"))
@@ -1,7 +1,7 @@
1
1
  import { resolveProductPaths } from "../foundation/lifecycle/index.js";
2
2
  import { applyConfiguredPiTheme, getAvailablePiThemes } from "../integrations/pi/components/index.js";
3
3
  import { createPiEngineAdapter } from "../integrations/pi/engine/index.js";
4
- import { OwnedUiSessionShell } from "../integrations/pi/owned-ui/index.js";
4
+ import { OwnedUiSessionShell } from "../integrations/pi/session-ui/index.js";
5
5
  import { OwnedUiSettingsSession, OwnedUiSettingsStore } from "../ui/settings/index.js";
6
6
  import { createPiTerminalBridge } from "../integrations/pi/tui-runtime/index.js";
7
7
  import { createOwnedRouteHost } from "./settings-route-host.js";
@@ -13,21 +13,32 @@ export async function composeOwnedUi(options = {}) {
13
13
  const adapter = options.createPiAdapter
14
14
  ? await options.createPiAdapter()
15
15
  : await createPiEngineAdapter({ cwd, availableThemes: () => getAvailablePiThemes().map(theme => theme.name) });
16
+ const ownedSurfaces = options.ownedSurfaces !== "off";
16
17
  const settings = options.profileId === undefined
17
18
  ? null
18
19
  : new OwnedUiSettingsSession({
19
20
  store: new OwnedUiSettingsStore({ configDir: resolveProductPaths().configDir, profileId: options.profileId }),
20
21
  agentProvider: () => adapter.settingsPort(),
22
+ ...(ownedSurfaces ? {
23
+ hiddenAgentSettingIds: ["tuiMode", "theme", "fullscreenScrollbar", "quietStartup"],
24
+ } : {}),
21
25
  });
22
- // Before anything is drawn, so every surface uses the configured theme rather
23
- // than the one guessed from the terminal's background.
24
- applyConfiguredPiTheme(adapter.configuredTheme());
25
- const routeHost = settings === null || options.ownedSurfaces === "off" ? null : createOwnedRouteHost(settings);
26
+ // Bare A1 intentionally ships one visual target while its UI is being completed:
27
+ // dark, regardless of terminal detection or a previously stored Pi theme. The
28
+ // comparison profile keeps Pi's configured theme behavior and settings surface.
29
+ applyConfiguredPiTheme(ownedSurfaces ? "dark" : adapter.configuredTheme());
30
+ const routeHost = settings === null || !ownedSurfaces ? null : createOwnedRouteHost(settings);
31
+ const viewportSettings = settings === null || !ownedSurfaces ? null : {
32
+ snapshot: () => viewportSettingsSnapshot(settings),
33
+ onChange: listener => settings.onChange(() => listener(viewportSettingsSnapshot(settings))),
34
+ };
26
35
  const shell = new OwnedUiSessionShell({
27
36
  backend: adapter,
28
37
  cwd,
29
38
  ...(options.terminal === undefined ? {} : { terminal: createPiTerminalBridge(options.terminal) }),
30
39
  ...(routeHost === null ? {} : { routeHost }),
40
+ ...(ownedSurfaces ? { sessionLayout: "custom-viewport" } : {}),
41
+ ...(viewportSettings === null ? {} : { viewportSettings }),
31
42
  });
32
43
  const application = {
33
44
  get disposed() { return adapter.disposed; },
@@ -38,3 +49,13 @@ export async function composeOwnedUi(options = {}) {
38
49
  };
39
50
  return { application, settings };
40
51
  }
52
+ function viewportSettingsSnapshot(settings) {
53
+ const appearance = settings.value("scrollbarAppearance");
54
+ const style = settings.value("scrollbarStyle");
55
+ const speed = settings.value("scrollbarSpeed");
56
+ return {
57
+ scrollbarAppearance: appearance === "always" || appearance === "hidden" ? appearance : "auto",
58
+ scrollbarStyle: style === "thick" ? "thick" : "thin",
59
+ scrollbarSpeed: speed === "high" ? "high" : speed === "fast" ? "fast" : "normal",
60
+ };
61
+ }
@@ -1,5 +1,5 @@
1
- import type { UiRouteHost } from "../integrations/pi/owned-ui/index.js";
2
1
  import type { OwnedUiSettingsSession } from "../ui/settings/index.js";
2
+ import { type UiRouteHost } from "../ui/apps/index.js";
3
3
  /**
4
4
  * Registers the A1-owned screens and adapts the app host to the shell's route
5
5
  * seam. The shell mounts whatever surface it is handed and forwards keys; it
@@ -1,6 +1,6 @@
1
1
  import { SETTINGS_APP_ID, SETTINGS_ROUTE, SettingsApp } from "../features/owned-ui/index.js";
2
2
  import { piTheme } from "../integrations/pi/components/index.js";
3
- import { UiAppHost, UiAppRegistry } from "../ui/apps/index.js";
3
+ import { UiAppHost, UiAppRegistry, } from "../ui/apps/index.js";
4
4
  import { faint } from "../ui/components/index.js";
5
5
  /**
6
6
  * Registers the A1-owned screens and adapts the app host to the shell's route
@@ -12,12 +12,28 @@ export interface OwnedUiModelInfo {
12
12
  readonly displayName: string;
13
13
  }
14
14
  export type OwnedUiThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
15
+ export interface OwnedUiViewportSettings {
16
+ readonly scrollbarAppearance: "auto" | "always" | "hidden";
17
+ readonly scrollbarStyle: "thin" | "thick";
18
+ /** normal is baseline; fast is 2x normal; high is normal plus fast. */
19
+ readonly scrollbarSpeed: "normal" | "fast" | "high";
20
+ }
21
+ /** Narrow live settings boundary consumed by the bare-A1 shell composition. */
22
+ export interface OwnedUiViewportSettingsPort {
23
+ snapshot(): OwnedUiViewportSettings;
24
+ onChange(listener: (settings: OwnedUiViewportSettings) => void): () => void;
25
+ }
15
26
  export interface OwnedUiTerminalSurface {
16
27
  readonly columns: number;
17
28
  readonly rows: number;
18
29
  readonly focusedRegion: "transcript" | "editor" | "dialog" | "overlay" | "status";
19
30
  readonly hardwareCursor: boolean;
20
31
  }
32
+ export interface OwnedUiImageAttachment {
33
+ readonly type: "image";
34
+ readonly data: string;
35
+ readonly mimeType: string;
36
+ }
21
37
  export interface OwnedUiEditorState {
22
38
  readonly text: string;
23
39
  readonly queuedSubmissions: readonly string[];
@@ -122,11 +138,13 @@ export type OwnedUiCommand = {
122
138
  readonly correlationId: OwnedUiCorrelationId;
123
139
  readonly sessionId: OwnedUiSessionId;
124
140
  readonly text: string;
141
+ readonly images?: readonly OwnedUiImageAttachment[];
125
142
  } | {
126
143
  readonly type: "steer" | "follow-up";
127
144
  readonly correlationId: OwnedUiCorrelationId;
128
145
  readonly sessionId: OwnedUiSessionId;
129
146
  readonly text: string;
147
+ readonly images?: readonly OwnedUiImageAttachment[];
130
148
  } | {
131
149
  readonly type: "abort" | "retry" | "compact" | "shutdown";
132
150
  readonly correlationId: OwnedUiCorrelationId;
@@ -189,6 +207,16 @@ export type OwnedUiEvent = {
189
207
  readonly sessionId: OwnedUiSessionId;
190
208
  readonly sequence: number;
191
209
  readonly block: OwnedUiTranscriptBlock;
210
+ } | {
211
+ /** One completed assistant reply; never a tool row or transcript rebuild. */
212
+ readonly type: "assistant-message-completed";
213
+ readonly sessionId: OwnedUiSessionId;
214
+ readonly sequence: number;
215
+ } | {
216
+ /** A fresh agent run has started, used by follow-mode surfaces. */
217
+ readonly type: "agent-run-started";
218
+ readonly sessionId: OwnedUiSessionId;
219
+ readonly sequence: number;
192
220
  } | {
193
221
  readonly type: "editor-state";
194
222
  readonly sessionId: OwnedUiSessionId;
@@ -5,6 +5,8 @@ const MAX_MESSAGE_LENGTH = 4_096;
5
5
  const MAX_TEXT_BYTES = 256 * 1024;
6
6
  const MAX_PAYLOAD_BYTES = 64 * 1024;
7
7
  const MAX_SESSION_VIEW_BYTES = 1024 * 1024;
8
+ const MAX_IMAGE_DATA_BYTES = 8 * 1024 * 1024;
9
+ const MAX_PROMPT_IMAGES = 8;
8
10
  const MAX_BLOCKS = 10_000;
9
11
  const MAX_CUSTOMIZATIONS = 1_000;
10
12
  const MAX_DIAGNOSTICS = 1_000;
@@ -53,6 +55,15 @@ export function assertOwnedUiCommand(command) {
53
55
  case "steer":
54
56
  case "follow-up":
55
57
  assertBoundedText(command.text, "owned-UI prompt text", MAX_TEXT_BYTES);
58
+ if (command.images !== undefined) {
59
+ assertCollection(command.images, "owned-UI prompt images", MAX_PROMPT_IMAGES);
60
+ for (const image of command.images) {
61
+ if (image.type !== "image")
62
+ throw new TypeError("owned-UI prompt image type is invalid");
63
+ assertBoundedText(image.data, "owned-UI prompt image data", MAX_IMAGE_DATA_BYTES);
64
+ assertBoundedText(image.mimeType, "owned-UI prompt image MIME type", MAX_LABEL_LENGTH);
65
+ }
66
+ }
56
67
  return;
57
68
  case "abort":
58
69
  case "retry":
@@ -103,6 +114,9 @@ export function assertOwnedUiEvent(event) {
103
114
  case "transcript-block":
104
115
  assertOwnedUiTranscriptBlock(event.block);
105
116
  return;
117
+ case "assistant-message-completed":
118
+ case "agent-run-started":
119
+ return;
106
120
  case "editor-state":
107
121
  assertOwnedUiEditorState(event.editor);
108
122
  return;
@@ -8,8 +8,16 @@ export function resolveDevelopmentLaunchEnvironment(canonicalPackageRoot, releas
8
8
  const instanceId = selectedInstanceId
9
9
  ? createHash("sha256").update(selectedInstanceId).digest("hex").slice(0, 16)
10
10
  : randomUUID().replaceAll("-", "").slice(0, 16);
11
- const developmentRoot = resolve(source[PRODUCT_IDENTITY.environment.developmentRoot]
12
- ?? join(temporaryDirectory, PRODUCT_IDENTITY.state.developmentDirectory, checkoutId, releaseId, "instances", instanceId));
11
+ const checkoutRoot = resolve(temporaryDirectory, PRODUCT_IDENTITY.state.developmentDirectory, checkoutId);
12
+ const selectedDevelopmentRoot = source[PRODUCT_IDENTITY.environment.developmentRoot];
13
+ const developmentRoot = resolve(selectedDevelopmentRoot
14
+ ?? join(checkoutRoot, releaseId, "instances", instanceId));
15
+ // Runtime/data remain instance-isolated, but product settings are user
16
+ // preferences and must survive a fresh local launch (and a rebuilt candidate).
17
+ // An explicit development root retains its historical self-contained policy.
18
+ const persistentConfigDir = selectedDevelopmentRoot
19
+ ? join(developmentRoot, "config")
20
+ : join(checkoutRoot, "config");
13
21
  const dataDir = resolve(source[PRODUCT_IDENTITY.environment.dataDir] ?? join(developmentRoot, "data"));
14
22
  return {
15
23
  checkoutId,
@@ -19,7 +27,7 @@ export function resolveDevelopmentLaunchEnvironment(canonicalPackageRoot, releas
19
27
  environment: {
20
28
  ...source,
21
29
  [PRODUCT_IDENTITY.environment.developmentInstanceId]: instanceId,
22
- [PRODUCT_IDENTITY.environment.configDir]: resolve(source[PRODUCT_IDENTITY.environment.configDir] ?? join(developmentRoot, "config")),
30
+ [PRODUCT_IDENTITY.environment.configDir]: resolve(source[PRODUCT_IDENTITY.environment.configDir] ?? persistentConfigDir),
23
31
  [PRODUCT_IDENTITY.environment.dataDir]: dataDir,
24
32
  [PRODUCT_IDENTITY.environment.runtimeDir]: resolve(source[PRODUCT_IDENTITY.environment.runtimeDir] ?? join(developmentRoot, "runtime")),
25
33
  [PRODUCT_IDENTITY.environment.databasePath]: resolve(source[PRODUCT_IDENTITY.environment.databasePath] ?? join(dataDir, PRODUCT_IDENTITY.endpoint.databaseFilename)),
@@ -3,5 +3,4 @@ export * from "./initialize-profile.js";
3
3
  export * from "./intent.js";
4
4
  export * from "./profile-paths.js";
5
5
  export * from "./prepare-launch.js";
6
- export * from "./profiles.js";
7
6
  export * from "./runtime-selection.js";
@@ -3,5 +3,4 @@ export * from "./initialize-profile.js";
3
3
  export * from "./intent.js";
4
4
  export * from "./profile-paths.js";
5
5
  export * from "./prepare-launch.js";
6
- export * from "./profiles.js";
7
6
  export * from "./runtime-selection.js";
@@ -1,7 +1,8 @@
1
- import { type LaunchProfileContract, type LaunchProfileId } from "./profiles.js";
1
+ import type { LaunchProfileId } from "../../foundation/lifecycle/index.js";
2
+ export type { LaunchProfileId } from "../../foundation/lifecycle/index.js";
2
3
  export interface InteractiveLaunchIntent {
3
4
  readonly kind: "interactive";
4
- readonly profile: LaunchProfileContract;
5
+ readonly profileId: LaunchProfileId;
5
6
  }
6
7
  export type LaunchIntent = InteractiveLaunchIntent;
7
8
  export declare function interactiveLaunchIntent(profileId: LaunchProfileId): InteractiveLaunchIntent;
@@ -1,4 +1,3 @@
1
- import { launchProfileContract } from "./profiles.js";
2
1
  export function interactiveLaunchIntent(profileId) {
3
- return Object.freeze({ kind: "interactive", profile: launchProfileContract(profileId) });
2
+ return Object.freeze({ kind: "interactive", profileId });
4
3
  }
@@ -2,9 +2,7 @@ import { type LaunchProfilePathOptions } from "./profile-paths.js";
2
2
  import { initializeProductProfile } from "./initialize-profile.js";
3
3
  import type { InteractiveLaunchIntent } from "./intent.js";
4
4
  export interface PreparedInteractiveLaunch {
5
- readonly intent: InteractiveLaunchIntent;
6
5
  readonly environment: NodeJS.ProcessEnv;
7
- readonly piArguments: readonly string[];
8
6
  readonly configurationRoot: string | null;
9
7
  }
10
8
  export interface PrepareInteractiveLaunchOptions extends LaunchProfilePathOptions {
@@ -2,7 +2,7 @@ import { configurationRootForProfile, resolveLaunchProfilePaths } from "./profil
2
2
  import { initializeProductProfile } from "./initialize-profile.js";
3
3
  export async function prepareInteractiveLaunch(intent, environment = process.env, options = {}) {
4
4
  const paths = resolveLaunchProfilePaths({ ...options, environment });
5
- const configurationRoot = configurationRootForProfile(intent.profile.id, paths);
5
+ const configurationRoot = configurationRootForProfile(intent.profileId, paths);
6
6
  if (configurationRoot !== null)
7
7
  await (options.initializeProfile ?? initializeProductProfile)(configurationRoot);
8
8
  const childEnvironment = { ...environment };
@@ -10,12 +10,5 @@ export async function prepareInteractiveLaunch(intent, environment = process.env
10
10
  delete childEnvironment.PI_CODING_AGENT_DIR;
11
11
  else
12
12
  childEnvironment.PI_CODING_AGENT_DIR = configurationRoot;
13
- const piArguments = intent.profile.projectTrust === "ignore" ? ["--no-approve"] : [];
14
- childEnvironment.A1_LAUNCH_ARGUMENTS_JSON = JSON.stringify(piArguments);
15
- return Object.freeze({
16
- intent,
17
- environment: childEnvironment,
18
- piArguments: Object.freeze(piArguments),
19
- configurationRoot,
20
- });
13
+ return Object.freeze({ environment: childEnvironment, configurationRoot });
21
14
  }
@@ -1,9 +1,8 @@
1
- import type { LaunchProfileId } from "./profiles.js";
1
+ import type { LaunchProfileId } from "./intent.js";
2
2
  export interface LaunchProfilePaths {
3
3
  readonly home: string;
4
4
  readonly managedStateRoot: string;
5
5
  readonly agentProfile: string;
6
- readonly sandbox: string;
7
6
  }
8
7
  export interface LaunchProfilePathOptions {
9
8
  readonly environment?: NodeJS.ProcessEnv;
@@ -7,20 +7,14 @@ export function resolveLaunchProfilePaths(options = {}) {
7
7
  const selectedHome = options.home ?? environment[PRODUCT_IDENTITY.environment.profileHome] ?? options.readHome?.() ?? homedir();
8
8
  const home = validateAbsolutePath(selectedHome, "effective user home", path);
9
9
  const agentProfile = path.resolve(home, PRODUCT_IDENTITY.state.piAgentProfile);
10
- const managedStateRoot = path.dirname(agentProfile);
11
10
  return Object.freeze({
12
11
  home,
13
- managedStateRoot,
12
+ managedStateRoot: path.dirname(agentProfile),
14
13
  agentProfile,
15
- sandbox: path.resolve(home, PRODUCT_IDENTITY.state.piSandboxProfile),
16
14
  });
17
15
  }
18
16
  export function configurationRootForProfile(profileId, paths) {
19
- if (profileId === "a1")
20
- return paths.agentProfile;
21
- if (profileId === "sandbox")
22
- return paths.sandbox;
23
- return null;
17
+ return profileId === "a1" ? paths.agentProfile : null;
24
18
  }
25
19
  function validateAbsolutePath(value, name, path) {
26
20
  if (value.length === 0 || value.includes("\0"))
@@ -1,10 +1,7 @@
1
1
  import { type LaunchProfileId } from "../../foundation/lifecycle/index.js";
2
2
  /**
3
- * Every interactive command is the same composition. What differs is the
4
- * configuration root each one reads and whether A1's own screens are reachable:
5
- * bare `a1` is the product, while `a1 pi` and `a1 sandbox` present pinned Pi's
6
- * interface and nothing of A1's own, against Pi's own profile and against an
7
- * isolated one.
3
+ * Both interactive commands use the owned composition. Bare A1 enables product
4
+ * surfaces; the Pi comparison command withholds them and reads Pi's own profile.
8
5
  */
9
6
  export type OwnedUiProfileId = LaunchProfileId;
10
7
  export interface InteractiveRuntimeSelection {
@@ -1,4 +1,4 @@
1
- import { GLOBAL_SCOPE, LineInput, PLAIN_THEME, ShortcutRegistry, PROMPT_GLYPH, blockJumpTarget, dialogValueColumn, numericValues, RAIL_COLUMNS, renderDialogPanel, renderEmptyState, renderGroupHeader, renderInputRow, renderStatusLine, renderListRow, renderNote, dialogRowAt, menuRowAt, regionAt, renderValueMenu, withScrollbarRail, stepperEnds, steppedValue, valueColumnFor, valueMenuFrame, promptRule, caretCell, blockRowSpan, displayWidth, faint, handleLineInputKey, humanizeLabel, humanizeTitle, indexOfKey, isThumbRow, layoutList, moveSelection, rowKey, scrollForSelection, scrollbarGeometry, selectableIndexes, } from "../../ui/components/index.js";
1
+ import { GLOBAL_SCOPE, LineInput, PLAIN_THEME, ShortcutRegistry, assertNoShortcutConflicts, PROMPT_GLYPH, blockJumpTarget, dialogValueColumn, numericValues, RAIL_COLUMNS, renderDialogPanel, renderEmptyState, renderGroupHeader, renderInputRow, renderStatusLine, renderListRow, renderNote, dialogRowAt, menuRowAt, regionAt, renderValueMenu, withScrollbarRail, stepperEnds, steppedValue, valueColumnFor, valueMenuFrame, promptRule, caretCell, blockRowSpan, displayWidth, faint, handleLineInputKey, humanizeLabel, humanizeTitle, indexOfKey, isThumbRow, layoutList, moveSelection, rowKey, scrollForSelection, scrollbarGeometry, selectableIndexes, } from "../../ui/components/index.js";
2
2
  export const SETTINGS_APP_ID = "settings";
3
3
  export const SETTINGS_ROUTE = "settings";
4
4
  const SCOPE = SETTINGS_APP_ID;
@@ -29,6 +29,7 @@ SETTINGS_SHORTCUTS.declare({ key: "left", scope: DIALOG_SCOPE, description: "Cha
29
29
  SETTINGS_SHORTCUTS.declare({ key: "right", scope: DIALOG_SCOPE, description: "Change this part", section: "Parts" }, "part-change");
30
30
  SETTINGS_SHORTCUTS.declare({ key: "up", scope: DIALOG_SCOPE, description: "Previous part", section: "Parts" }, "part-previous");
31
31
  SETTINGS_SHORTCUTS.declare({ key: "down", scope: DIALOG_SCOPE, description: "Next part", section: "Parts" }, "part-next");
32
+ assertNoShortcutConflicts(SETTINGS_SHORTCUTS.assemble());
32
33
  const KEYS = {
33
34
  "\u001b[A": "up",
34
35
  "\u001b[B": "down",
@@ -56,6 +57,8 @@ export class SettingsApp {
56
57
  #session;
57
58
  #selectedKey;
58
59
  #scroll = 0;
60
+ /** Keyboard navigation requests visibility once; pointer scrolling then stays free. */
61
+ #selectionNeedsReveal = true;
59
62
  #reveal;
60
63
  #notice = null;
61
64
  #filter = null;
@@ -96,9 +99,13 @@ export class SettingsApp {
96
99
  this.#panelTop = this.#panelTopForFrame;
97
100
  const bodyHeight = Math.max(0, rect.height - footer.length);
98
101
  const selected = indexOfKey(rows, this.#selectedKey);
99
- this.#scroll = scrollForSelection(rows, bodyHeight, this.#scroll, selected, this.#reveal);
102
+ if (this.#selectionNeedsReveal) {
103
+ this.#scroll = scrollForSelection(rows, bodyHeight, this.#scroll, selected, this.#reveal);
104
+ this.#selectionNeedsReveal = false;
105
+ }
100
106
  this.#reveal = undefined;
101
107
  const layout = layoutList(rows, bodyHeight, this.#scroll);
108
+ this.#scroll = layout.scroll;
102
109
  const contentWidth = Math.max(0, rect.width - RAIL_COLUMNS);
103
110
  const valueColumn = this.#valueColumn(rows);
104
111
  const geometry = scrollbarGeometry({
@@ -121,14 +128,13 @@ export class SettingsApp {
121
128
  for (const index of layout.rowIndexes) {
122
129
  const row = rows[index];
123
130
  if (row !== undefined && row.kind === "element") {
131
+ const view = this.#viewRow(row.value);
124
132
  this.#frameRows.push({
125
- key: `${row.value.backend}:${row.value.id}`,
133
+ key: view.key,
126
134
  screenRow: body.length,
127
135
  valueColumn,
128
- valueWidth: row.value.structured
129
- ? CONFIGURE.length
130
- : row.value.value === null ? 0 : displayWidth(displayValue(row.value.value)),
131
- stepper: isStepper(row.value),
136
+ valueWidth: displayWidth(view.value),
137
+ stepper: view.stepper !== undefined,
132
138
  });
133
139
  }
134
140
  body.push(this.#renderRow(row, index === selected, contentWidth, valueColumn, theme));
@@ -189,8 +195,11 @@ export class SettingsApp {
189
195
  // End lands on the very last setting, not on the head of its section.
190
196
  const selectable = selectableIndexes(rows);
191
197
  const target = action === "last" ? selectable.at(-1) : selectable[0];
192
- if (target !== undefined)
193
- this.#jump(rows, target);
198
+ if (target !== undefined) {
199
+ this.#select(rows, target);
200
+ if (action === "first")
201
+ this.#scroll = 0;
202
+ }
194
203
  return { consumed: true };
195
204
  }
196
205
  case "previous-value":
@@ -263,6 +272,14 @@ export class SettingsApp {
263
272
  }
264
273
  return { consumed: true, render: false };
265
274
  }
275
+ if (event.kind === "wheel-up" || event.kind === "wheel-down") {
276
+ // The whole list pane owns wheel scrolling, including blank space beside
277
+ // short labels. It must not depend on finding an item under the pointer.
278
+ if (event.row < 1 || event.row > this.#panelTopForFrame)
279
+ return { consumed: false };
280
+ this.#scroll = Math.max(0, this.#scroll + (event.kind === "wheel-down" ? 3 : -3));
281
+ return { consumed: true };
282
+ }
266
283
  const row = this.#frameRows.find(candidate => candidate.screenRow === event.row - 1);
267
284
  const previousKey = this.#hoverKey;
268
285
  const previousRegion = this.#hoverRegion;
@@ -288,10 +305,6 @@ export class SettingsApp {
288
305
  }
289
306
  return { consumed: true };
290
307
  }
291
- if (event.kind === "wheel-up" || event.kind === "wheel-down") {
292
- this.#scroll = Math.max(0, this.#scroll + (event.kind === "wheel-down" ? 3 : -3));
293
- return { consumed: true };
294
- }
295
308
  const changed = previousKey !== this.#hoverKey || previousRegion !== this.#hoverRegion;
296
309
  return { consumed: event.kind !== "motion", render: changed };
297
310
  }
@@ -403,6 +416,17 @@ export class SettingsApp {
403
416
  if (input === null)
404
417
  return { consumed: false };
405
418
  const key = KEYS[data];
419
+ if (key === "home" || key === "end") {
420
+ const rows = this.#rows();
421
+ const selectable = selectableIndexes(rows);
422
+ const target = key === "end" ? selectable.at(-1) : selectable[0];
423
+ if (target !== undefined) {
424
+ this.#select(rows, target);
425
+ if (key === "home")
426
+ this.#scroll = 0;
427
+ }
428
+ return { consumed: true };
429
+ }
406
430
  if (key === "up" || key === "down" || key === "shift+up" || key === "shift+down") {
407
431
  const rows = this.#rows();
408
432
  // Nothing found means nothing to move through; the key is still swallowed
@@ -485,6 +509,7 @@ export class SettingsApp {
485
509
  if (index < 0)
486
510
  return;
487
511
  this.#selectedKey = rowKey(rows[index]);
512
+ this.#selectionNeedsReveal = true;
488
513
  this.#notice = null;
489
514
  }
490
515
  #rows() {
@@ -554,7 +579,6 @@ export class SettingsApp {
554
579
  key: `${entry.backend}:${entry.id}`,
555
580
  label: labelOf(entry),
556
581
  value,
557
- ...(entry.origin === "default" ? { suffix: "(default)" } : {}),
558
582
  ...(typeof shown === "number" && entry.editable ? { stepper: stepperEnds(range, shown) } : {}),
559
583
  };
560
584
  }
@@ -1,4 +1,4 @@
1
- export type LaunchProfileId = "a1" | "pi" | "sandbox";
1
+ export type LaunchProfileId = "a1" | "pi";
2
2
  export type GenerationId = string;
3
3
  export type DriverProfileId = string;
4
4
  export type RequestId = string;
@@ -1,6 +1,6 @@
1
1
  import { PRODUCT_TEXT } from "../../product-identity.js";
2
2
  export function assertLaunchProfileId(value) {
3
- if (value !== "a1" && value !== "pi" && value !== "sandbox")
3
+ if (value !== "a1" && value !== "pi")
4
4
  throw new TypeError(PRODUCT_TEXT.diagnostic(`launch profile is invalid: ${String(value)}`));
5
5
  }
6
6
  export function assertDimensions(dimensions) {
@@ -7,9 +7,7 @@ export interface BootstrapOptions {
7
7
  readonly packageRoot: string;
8
8
  readonly launchIntent?: {
9
9
  readonly kind: "interactive";
10
- readonly profile: {
11
- readonly id: LaunchProfileId;
12
- };
10
+ readonly profileId: LaunchProfileId;
13
11
  };
14
12
  readonly environment?: NodeJS.ProcessEnv;
15
13
  readonly output?: Pick<NodeJS.WriteStream, "write">;
@@ -13,7 +13,7 @@ import { consumeMaterializationProof, materializeRelease, readCertifiedReleaseMa
13
13
  import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
14
14
  export async function runBootstrap(options) {
15
15
  const environment = { ...(options.environment ?? process.env) };
16
- const launchProfileId = options.launchIntent?.profile.id ?? "a1";
16
+ const launchProfileId = options.launchIntent?.profileId ?? "a1";
17
17
  assertLaunchProfileId(launchProfileId);
18
18
  environment[PRODUCT_IDENTITY.environment.launchProfile] = launchProfileId;
19
19
  const output = options.output ?? process.stderr;
@@ -30,8 +30,8 @@ export class ControlStore {
30
30
  migrate() {
31
31
  const versionRow = this.database.prepare("PRAGMA user_version").get();
32
32
  const version = versionRow.user_version;
33
- if (version > 5)
34
- throw new Error(`control database version ${version} is newer than supported version 5`);
33
+ if (version > 6)
34
+ throw new Error(`control database version ${version} is newer than supported version 6`);
35
35
  if (version !== 0)
36
36
  this.#assertCurrentControlSchema();
37
37
  if (version === 0) {
@@ -187,7 +187,7 @@ export class ControlStore {
187
187
  CREATE TABLE launch_instances (
188
188
  id TEXT PRIMARY KEY,
189
189
  owner_client_id TEXT NOT NULL,
190
- profile_id TEXT NOT NULL CHECK (profile_id IN ('a1', 'pi', 'sandbox')),
190
+ profile_id TEXT NOT NULL CHECK (profile_id IN ('a1', 'pi')),
191
191
  state TEXT NOT NULL CHECK (state IN ('requested', 'active', 'stopping', 'completed', 'interrupted')),
192
192
  shutdown_policy TEXT NOT NULL CHECK (shutdown_policy = 'terminate-tree-on-close'),
193
193
  guardian_identity_json TEXT NOT NULL CHECK (json_valid(guardian_identity_json)),
@@ -208,6 +208,40 @@ export class ControlStore {
208
208
  CREATE INDEX idx_launch_instances_boot ON launch_instances(owner_boot_nonce, state);
209
209
  PRAGMA user_version = 5;
210
210
  COMMIT;
211
+ `);
212
+ }
213
+ if (version <= 5) {
214
+ this.database.exec(`
215
+ BEGIN IMMEDIATE;
216
+ ALTER TABLE launch_instances RENAME TO launch_instances_v5;
217
+ DROP INDEX idx_launch_instances_owner;
218
+ DROP INDEX idx_launch_instances_boot;
219
+ CREATE TABLE launch_instances (
220
+ id TEXT PRIMARY KEY,
221
+ owner_client_id TEXT NOT NULL,
222
+ profile_id TEXT NOT NULL CHECK (profile_id IN ('a1', 'pi')),
223
+ state TEXT NOT NULL CHECK (state IN ('requested', 'active', 'stopping', 'completed', 'interrupted')),
224
+ shutdown_policy TEXT NOT NULL CHECK (shutdown_policy = 'terminate-tree-on-close'),
225
+ guardian_identity_json TEXT NOT NULL CHECK (json_valid(guardian_identity_json)),
226
+ root_identity_json TEXT CHECK (root_identity_json IS NULL OR json_valid(root_identity_json)),
227
+ containment_identity_json TEXT CHECK (containment_identity_json IS NULL OR json_valid(containment_identity_json)),
228
+ created_at TEXT NOT NULL,
229
+ activated_at TEXT,
230
+ stopping_at TEXT,
231
+ completed_at TEXT,
232
+ outcome_json TEXT CHECK (outcome_json IS NULL OR json_valid(outcome_json)),
233
+ owner_boot_nonce TEXT NOT NULL,
234
+ CHECK ((state = 'requested' AND root_identity_json IS NULL AND containment_identity_json IS NULL AND activated_at IS NULL AND stopping_at IS NULL AND completed_at IS NULL AND outcome_json IS NULL)
235
+ OR (state = 'active' AND root_identity_json IS NOT NULL AND containment_identity_json IS NOT NULL AND activated_at IS NOT NULL AND stopping_at IS NULL AND completed_at IS NULL AND outcome_json IS NULL)
236
+ OR (state = 'stopping' AND root_identity_json IS NOT NULL AND containment_identity_json IS NOT NULL AND activated_at IS NOT NULL AND stopping_at IS NOT NULL AND completed_at IS NULL AND outcome_json IS NULL)
237
+ OR (state IN ('completed', 'interrupted') AND completed_at IS NOT NULL AND outcome_json IS NOT NULL))
238
+ );
239
+ INSERT INTO launch_instances SELECT * FROM launch_instances_v5 WHERE profile_id IN ('a1', 'pi');
240
+ DROP TABLE launch_instances_v5;
241
+ CREATE INDEX idx_launch_instances_owner ON launch_instances(owner_client_id, state);
242
+ CREATE INDEX idx_launch_instances_boot ON launch_instances(owner_boot_nonce, state);
243
+ PRAGMA user_version = 6;
244
+ COMMIT;
211
245
  `);
212
246
  }
213
247
  if (version === 0) {