@timurproko/a1 0.1.8-dev.139 → 0.1.8-dev.157

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 (59) hide show
  1. package/README.md +21 -9
  2. package/bin/cli.js +1 -0
  3. package/dist/cli/dispatch.d.ts +6 -0
  4. package/dist/cli/dispatch.js +107 -81
  5. package/dist/cli/version-stats.js +7 -1
  6. package/dist/composition/owned-ui.js +10 -7
  7. package/dist/composition/settings-route-host.d.ts +1 -1
  8. package/dist/composition/settings-route-host.js +1 -1
  9. package/dist/contracts/owned-ui/model.d.ts +8 -1
  10. package/dist/contracts/owned-ui/validation.js +11 -0
  11. package/dist/features/owned-ui/settings-app.js +2 -1
  12. package/dist/integrations/pi/components/owned-editor-ux.d.ts +55 -0
  13. package/dist/integrations/pi/components/owned-editor-ux.js +778 -0
  14. package/dist/integrations/pi/components/shell-editor-autocomplete.js +44 -6
  15. package/dist/integrations/pi/components/shell-shared-facade.d.ts +41 -7
  16. package/dist/integrations/pi/components/shell-shared-facade.js +3 -1
  17. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +10 -2
  18. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +36 -3
  19. package/dist/integrations/pi/engine/adapter.js +5 -1
  20. package/dist/integrations/pi/engine/session-integration.d.ts +2 -1
  21. package/dist/integrations/pi/engine/session-integration.js +14 -3
  22. package/dist/integrations/pi/session-ui/prompt-chips.d.ts +23 -0
  23. package/dist/integrations/pi/session-ui/prompt-chips.js +242 -0
  24. package/dist/integrations/pi/session-ui/route-host.d.ts +5 -0
  25. package/dist/integrations/pi/{owned-ui → session-ui}/session-shell-root.d.ts +17 -3
  26. package/dist/integrations/pi/{owned-ui → session-ui}/session-shell-root.js +100 -260
  27. package/dist/integrations/pi/{owned-ui → session-ui}/session-shell.d.ts +1 -1
  28. package/dist/integrations/pi/{owned-ui → session-ui}/session-shell.js +52 -12
  29. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +39 -0
  30. package/dist/integrations/pi/session-ui/session-viewport-controller.js +369 -0
  31. package/dist/integrations/pi/session-ui/system-clipboard.d.ts +11 -0
  32. package/dist/integrations/pi/session-ui/system-clipboard.js +101 -0
  33. package/dist/integrations/pi/tui-runtime/contracts.d.ts +2 -6
  34. package/dist/native/darwin-arm64/manifest.json +1 -1
  35. package/dist/native/linux-x64/manifest.json +3 -3
  36. package/dist/native/linux-x64/process-guardian +0 -0
  37. package/dist/native/win32-x64/manifest.json +3 -3
  38. package/dist/native/win32-x64/process-guardian.exe +0 -0
  39. package/dist/ui/apps/contracts.d.ts +23 -1
  40. package/dist/ui/apps/host.js +9 -1
  41. package/dist/ui/components/scrollbar.d.ts +2 -2
  42. package/dist/ui/components/scrollbar.js +3 -3
  43. package/dist/ui/components/spans.d.ts +16 -1
  44. package/dist/ui/components/spans.js +144 -2
  45. package/dist/ui/components/text.js +24 -2
  46. package/dist/ui/components/transcript-viewport.d.ts +6 -0
  47. package/dist/ui/components/transcript-viewport.js +36 -8
  48. package/dist/ui/settings/declarations.d.ts +1 -1
  49. package/dist/ui/settings/declarations.js +3 -3
  50. package/dist/ui/settings/migrations.js +9 -0
  51. package/docs/architecture/boundaries.md +1 -1
  52. package/docs/architecture/project-structure.md +1 -1
  53. package/docs/ci-release-runbook.md +6 -5
  54. package/docs/features/launch-profiles.md +3 -3
  55. package/package.json +4 -1
  56. package/dist/integrations/pi/owned-ui/route-host.d.ts +0 -29
  57. /package/dist/integrations/pi/{owned-ui → session-ui}/index.d.ts +0 -0
  58. /package/dist/integrations/pi/{owned-ui → session-ui}/index.js +0 -0
  59. /package/dist/integrations/pi/{owned-ui → session-ui}/route-host.js +0 -0
package/README.md CHANGED
@@ -23,20 +23,28 @@ npm install -g @timurproko/a1@0.1.8-dev.107
23
23
  ## Use
24
24
 
25
25
  ```sh
26
- a1 # launch A1 (profile: ~/.a1/agent)
27
- a1 version # show Current, Develop, and Release versions
28
- a1 update # install the newest stable release
29
- a1 update:develop # install the current development preview
30
- a1 update:107 # install numbered preview 107
31
- a1 update --models # refresh A1's model catalogs
26
+ a1 # launch A1 (profile: ~/.a1/agent)
27
+ a1 --help # show all commands (short form: a1 -h)
28
+ a1 --version # show the version (short form: a1 -v)
29
+ a1 update # install the newest stable release
30
+ a1 update --develop # install the current development preview
31
+ a1 update --develop 107 # install numbered preview 107
32
+ a1 update --develop 0.1.8-dev.107 # install that exact preview
33
+ a1 update --models # refresh A1's model catalogs
32
34
  ```
33
35
 
36
+ Stable builds print only their installed version. Development builds also show the
37
+ current development and stable channel versions.
38
+
34
39
  Development previews add the Pi comparison profile; release builds do not carry it.
35
40
 
36
41
  ```sh
37
- a1 pi # vanilla Pi oracle: ~/.pi/agent
42
+ a1 pi # vanilla Pi oracle: ~/.pi/agent
38
43
  ```
39
44
 
45
+ Unsupported commands exit quietly without launching anything. The removed
46
+ `update:<preview>` forms are not aliases; use `update --develop`.
47
+
40
48
  ## Extensions
41
49
 
42
50
  Pi extension packages install into A1's own profile (`~/.a1/agent`), so bare `a1`
@@ -49,8 +57,12 @@ a1 pi remove npm:pi-mcp-adapter # remove it (alias: a1 pi uninstall)
49
57
  a1 pi list # list installed packages
50
58
  a1 pi update --extensions # update every installed package
51
59
  a1 pi update npm:pi-mcp-adapter # update one
60
+ a1 pi update --models # refresh A1's model catalogs
52
61
  ```
53
62
 
63
+ A1 pins the Pi runtime carried by each release, so Pi self-update forms are refused.
64
+ Update A1 itself with `a1 update` or `a1 update --develop`.
65
+
54
66
  A running session picks up a newly installed package after a restart.
55
67
  Configuration is isolated the same way: bare `a1` reads `~/.a1/agent` (or the
56
68
  project), never `~/.pi/agent`. Configure MCP with `/mcp setup` inside bare `a1`.
@@ -88,8 +100,8 @@ npm run develop # request the preview publish run and wait for it
88
100
  Install:
89
101
 
90
102
  ```sh
91
- a1 update:107 # install preview 107
92
- a1 update:0.1.8-dev.107 # install that exact full preview version
103
+ a1 update --develop 107 # install preview 107
104
+ a1 update --develop 0.1.8-dev.107 # install that exact full preview version
93
105
  ```
94
106
 
95
107
  ### Stable
package/bin/cli.js CHANGED
@@ -45,5 +45,6 @@ process.exitCode = await dispatchCli(process.argv.slice(2), {
45
45
  return await runPackageCommand(request, { createPort: createPiPackagesPort });
46
46
  },
47
47
  }, {
48
+ stdout: message => process.stdout.write(message),
48
49
  stderr: message => process.stderr.write(message),
49
50
  }, capabilities);
@@ -9,11 +9,17 @@ export interface CliHandlers {
9
9
  readonly packages: (request: PackageCommandRequest) => Promise<number>;
10
10
  }
11
11
  export interface CliOutput {
12
+ readonly stdout: (message: string) => void;
12
13
  readonly stderr: (message: string) => void;
13
14
  }
14
15
  export declare function cliUsage(capabilities: CliCapabilities): string;
16
+ export declare function cliHelp(capabilities: CliCapabilities): string;
15
17
  export declare function dispatchCli(arguments_: readonly string[], handlers: CliHandlers, output: CliOutput, capabilities: CliCapabilities): Promise<number>;
16
18
  export type CliCommand = {
19
+ readonly kind: "noop";
20
+ } | {
21
+ readonly kind: "help";
22
+ } | {
17
23
  readonly kind: "launch";
18
24
  readonly profileId: LaunchProfileId;
19
25
  } | {
@@ -4,21 +4,60 @@ export function cliUsage(capabilities) {
4
4
  return PRODUCT_TEXT.usage([
5
5
  "",
6
6
  ...(capabilities.developmentComparison ? ["pi"] : []),
7
- "version",
8
- "update [self|--models]",
9
- "update:develop",
10
- "update:<number>",
7
+ "--help",
8
+ "-h",
9
+ "--version",
10
+ "-v",
11
+ "update",
12
+ "update --develop [preview-or-version]",
13
+ "update --models",
11
14
  "pi install <source>",
12
15
  "pi remove <source>",
16
+ "pi uninstall <source>",
13
17
  "pi list",
14
- "pi update [--extensions|<source>]",
18
+ "pi update --extensions",
19
+ "pi update --models",
20
+ "pi update <source>",
15
21
  ]);
16
22
  }
23
+ export function cliHelp(capabilities) {
24
+ const command = PRODUCT_TEXT.commandName;
25
+ return [
26
+ "Common:",
27
+ ` ${command}`,
28
+ ...(capabilities.developmentComparison ? [` ${command} pi`] : []),
29
+ ` ${command} --help`,
30
+ ` ${command} -h`,
31
+ ` ${command} --version`,
32
+ ` ${command} -v`,
33
+ "",
34
+ "Update:",
35
+ ` ${command} update`,
36
+ ` ${command} update --develop [preview-or-version]`,
37
+ ` ${command} update --models`,
38
+ "",
39
+ "Pi-compatible packages:",
40
+ ` ${command} pi install <source>`,
41
+ ` ${command} pi remove <source>`,
42
+ ` ${command} pi uninstall <source>`,
43
+ ` ${command} pi list`,
44
+ ` ${command} pi update --extensions`,
45
+ ` ${command} pi update --models`,
46
+ ` ${command} pi update <source>`,
47
+ "",
48
+ ].join("\n");
49
+ }
17
50
  const PROFILE_WORDS = new Set(["pi"]);
18
51
  export async function dispatchCli(arguments_, handlers, output, capabilities) {
19
52
  const command = parseCliCommand(arguments_, capabilities);
53
+ if (command.kind === "noop")
54
+ return 0;
55
+ if (command.kind === "help") {
56
+ output.stdout(cliHelp(capabilities));
57
+ return 0;
58
+ }
20
59
  if (command.kind === "error") {
21
- output.stderr(`${command.message}\n${cliUsage(capabilities)}\n`);
60
+ output.stderr(`${command.message}\n`);
22
61
  return 2;
23
62
  }
24
63
  if (command.kind === "launch")
@@ -33,76 +72,63 @@ export function parseCliCommand(arguments_, capabilities) {
33
72
  if (arguments_.length === 0)
34
73
  return { kind: "launch", profileId: "a1" };
35
74
  const [command, ...rest] = arguments_;
75
+ if (command === "--help" || command === "-h")
76
+ return withoutArguments(rest, { kind: "help" });
77
+ if (command === "--version" || command === "-v")
78
+ return withoutArguments(rest, { kind: "version" });
36
79
  if (command === "pi") {
37
80
  if (rest.length > 0)
38
81
  return parsePiPackageCommand(rest);
39
- if (capabilities.developmentComparison)
40
- return { kind: "launch", profileId: "pi" };
82
+ return capabilities.developmentComparison ? { kind: "launch", profileId: "pi" } : { kind: "noop" };
41
83
  }
42
- if (command === "version")
43
- return withoutArguments(rest, { kind: "version" });
44
- if (command !== undefined && command.startsWith("update:"))
45
- return parseColonUpdate(command.slice("update:".length), rest);
46
84
  if (command === "update")
47
85
  return parseUpdate(rest);
48
- if (command === "install" || command === "remove" || command === "uninstall" || command === "list") {
49
- return packageNamespaceRejection(command, rest.join(" ") || undefined);
50
- }
51
- if (command === "ui")
52
- return { kind: "error", message: `The ui subcommand was removed; run bare ${PRODUCT_TEXT.commandName} for the owned UI.` };
53
- if (command === "agent")
54
- return { kind: "error", message: `Bare ${PRODUCT_TEXT.commandName} is the ${PRODUCT_TEXT.displayName} agent experience; there is no agent subcommand.` };
55
- return { kind: "error", message: PRODUCT_TEXT.diagnostic(`received an unknown command: ${command ?? ""}`) };
86
+ if (command?.startsWith("update:"))
87
+ return { kind: "noop" };
88
+ // Unsupported and reserved command spaces are deliberately quiet. Help is
89
+ // explicit, and a typo must never start an interactive or maintenance path.
90
+ return { kind: "noop" };
56
91
  }
57
92
  /**
58
- * What follows the colon says which development build to move to. `develop`
59
- * selects the channel head; a positive decimal or a full numbered preview names
60
- * one immutable publication.
61
- */
62
- function parseColonUpdate(suffix, rest) {
63
- if (rest.length > 0)
64
- return { kind: "error", message: PRODUCT_TEXT.diagnostic("update takes what to move to after the colon, and nothing else.") };
65
- if (suffix === "next") {
66
- return { kind: "error", message: PRODUCT_TEXT.diagnostic(`renamed its development channel; run ${PRODUCT_TEXT.commandName} update:develop.`) };
67
- }
68
- if (suffix === "develop")
69
- return { kind: "update", channel: "next" };
70
- if (suffix.length === 0)
71
- return { kind: "error", message: PRODUCT_TEXT.diagnostic(`update: needs a preview after the colon, as in ${PRODUCT_TEXT.commandName} update:develop.`) };
72
- if (!/^[1-9]\d*$/.test(suffix) && !/^\d+\.\d+\.\d+-dev\.[1-9]\d*$/.test(suffix)) {
73
- return { kind: "error", message: PRODUCT_TEXT.diagnostic(`received an unusable numbered preview: ${suffix}`) };
74
- }
75
- return { kind: "update", channel: "next", target: suffix };
76
- }
77
- /**
78
- * Top-level `update` owns A1 itself and A1's model catalogs. Extension package
79
- * maintenance lives under the `pi` compatibility namespace, while updating the
80
- * pinned Pi runtime remains impossible.
93
+ * Stable update is the empty form. `--develop` selects the moving development
94
+ * head or one immutable numbered preview, while model refresh remains a separate
95
+ * operation that cannot be combined with either update channel.
81
96
  */
82
97
  function parseUpdate(rest) {
83
98
  if (rest.length === 0)
84
99
  return { kind: "update", channel: "stable" };
85
- if (rest.length > 1)
86
- return { kind: "error", message: PRODUCT_TEXT.diagnostic("update accepts one target.") };
87
- const [target] = rest;
88
- if (target === "self")
89
- return { kind: "update", channel: "stable" };
90
- if (target === "pi") {
91
- return {
92
- kind: "error",
93
- message: PRODUCT_TEXT.diagnostic(`pins the Pi version it was certified against; run ${PRODUCT_TEXT.commandName} update to move ${PRODUCT_TEXT.displayName} itself.`),
94
- };
95
- }
96
- if (target === "next" || target === "develop" || target === "stable") {
97
- const form = target === "stable" ? `${PRODUCT_TEXT.commandName} update` : `${PRODUCT_TEXT.commandName} update:develop`;
98
- return { kind: "error", message: PRODUCT_TEXT.diagnostic(`selects a release channel with a colon; run ${form}.`) };
100
+ const [selector, ...values] = rest;
101
+ // Removed compatibility notation is unsupported rather than deprecated.
102
+ if (selector === "self")
103
+ return { kind: "noop" };
104
+ if (selector === "--develop") {
105
+ if (values.length === 0)
106
+ return { kind: "update", channel: "next" };
107
+ if (values.length > 1)
108
+ return updateGrammarError("--develop accepts at most one preview number or version.");
109
+ const [target] = values;
110
+ if (target === undefined || !isDevelopmentTarget(target)) {
111
+ return updateGrammarError(`--develop received an unusable preview: ${target ?? ""}`);
112
+ }
113
+ return { kind: "update", channel: "next", target };
99
114
  }
100
- if (target === "--models")
115
+ if (selector === "--models") {
116
+ if (values.length > 0)
117
+ return updateGrammarError("--models cannot be combined with another update selector.");
101
118
  return { kind: "packages", request: { verb: "refresh-models", source: null } };
102
- if (target === "--extensions" || (target !== undefined && !target.startsWith("-"))) {
103
- return packageNamespaceRejection("update", target === "--extensions" ? "--extensions" : target);
104
119
  }
105
- return unknownOption(target ?? "", "update");
120
+ if (selector === "pi")
121
+ return pinnedPiUpdateError();
122
+ if (selector !== undefined && selector.startsWith("-"))
123
+ return unknownOption(selector, "update");
124
+ // Positional update targets are reserved for a future A1-native plugin model.
125
+ return { kind: "noop" };
126
+ }
127
+ function isDevelopmentTarget(target) {
128
+ return /^[1-9]\d*$/.test(target) || /^\d+\.\d+\.\d+-dev\.[1-9]\d*$/.test(target);
129
+ }
130
+ function updateGrammarError(detail) {
131
+ return { kind: "error", message: PRODUCT_TEXT.diagnostic(`could not parse update: ${detail}`) };
106
132
  }
107
133
  function parsePiPackageCommand(arguments_) {
108
134
  const [verb, ...rest] = arguments_;
@@ -113,30 +139,40 @@ function parsePiPackageCommand(arguments_) {
113
139
  return withoutArguments(rest, { kind: "packages", request: { verb: "list", source: null } });
114
140
  if (verb === "update")
115
141
  return parsePiPackageUpdate(rest);
116
- return { kind: "error", message: PRODUCT_TEXT.diagnostic(`received an unknown pi package command: ${verb ?? ""}`) };
142
+ return { kind: "noop" };
117
143
  }
118
144
  function parsePiPackageUpdate(rest) {
119
- if (rest.length === 0) {
120
- return { kind: "error", message: PRODUCT_TEXT.diagnostic(`pi update needs --extensions or a package source.`) };
121
- }
145
+ if (rest.length === 0)
146
+ return pinnedPiUpdateError();
122
147
  if (rest.length > 1)
123
148
  return { kind: "error", message: PRODUCT_TEXT.diagnostic("pi update accepts one target.") };
124
149
  const [target] = rest;
125
150
  if (target === "--extensions")
126
151
  return { kind: "packages", request: { verb: "update", source: null } };
127
- if (target === "--models") {
128
- return { kind: "error", message: PRODUCT_TEXT.diagnostic(`refreshes its model catalogs at the top level; run ${PRODUCT_TEXT.commandName} update --models.`) };
129
- }
152
+ if (target === "--models")
153
+ return { kind: "packages", request: { verb: "refresh-models", source: null } };
154
+ if (target === "--self" || target === "--all" || target === "self" || target === "pi")
155
+ return pinnedPiUpdateError();
130
156
  if (target === undefined || target.startsWith("-"))
131
157
  return unknownOption(target ?? "", "pi update");
132
- if (PROFILE_WORDS.has(target))
133
- return profileRejection("update");
134
158
  return { kind: "packages", request: { verb: "update", source: target } };
135
159
  }
160
+ function pinnedPiUpdateError() {
161
+ return {
162
+ kind: "error",
163
+ message: [
164
+ PRODUCT_TEXT.diagnostic("pins its certified Pi runtime and cannot update it independently."),
165
+ "Use:",
166
+ ` ${PRODUCT_TEXT.commandName} update Update A1`,
167
+ ` ${PRODUCT_TEXT.commandName} pi update --extensions Update Pi-compatible packages`,
168
+ ` ${PRODUCT_TEXT.commandName} pi update --models Refresh model catalogs`,
169
+ ].join("\n"),
170
+ };
171
+ }
136
172
  function parseSourceCommand(verb, rest) {
137
173
  const flag = rest.find(argument => argument.startsWith("-"));
138
174
  if (flag !== undefined)
139
- return unknownOption(flag, verb);
175
+ return unknownOption(flag, `pi ${verb}`);
140
176
  if (rest.length === 0)
141
177
  return { kind: "error", message: PRODUCT_TEXT.diagnostic(`${verb} requires a package source.`) };
142
178
  if (rest.length > 1)
@@ -151,18 +187,8 @@ function parseSourceCommand(verb, rest) {
151
187
  function withoutArguments(rest, command) {
152
188
  if (rest.length === 0)
153
189
  return command;
154
- const [argument] = rest;
155
- if (argument !== undefined && (PROFILE_WORDS.has(argument) || argument.startsWith("--profile")))
156
- return profileRejection("list");
157
190
  return { kind: "error", message: PRODUCT_TEXT.diagnostic("commands do not accept additional arguments.") };
158
191
  }
159
- function packageNamespaceRejection(verb, target) {
160
- const suffix = target === undefined ? "" : ` ${target}`;
161
- return {
162
- kind: "error",
163
- message: PRODUCT_TEXT.diagnostic(`manages extension packages under its pi namespace; run ${PRODUCT_TEXT.commandName} pi ${verb}${suffix}.`),
164
- };
165
- }
166
192
  function profileRejection(verb) {
167
193
  return {
168
194
  kind: "error",
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { resolve } from "node:path";
4
4
  import crossSpawn from "cross-spawn";
5
- import { valid as validSemver } from "semver";
5
+ import { prerelease, valid as validSemver } from "semver";
6
6
  import { PRODUCT_TEXT } from "../product-identity.js";
7
7
  const defaultOutput = {
8
8
  stdout(message) { process.stdout.write(message); },
@@ -20,6 +20,12 @@ export async function runVersionStats(options) {
20
20
  output.stderr(`${PRODUCT_TEXT.diagnostic(`could not read its installed version: ${message(error)}`)}\n`);
21
21
  return 1;
22
22
  }
23
+ // Match Pi's release behavior: stable builds print only their installed version.
24
+ // Development builds retain channel visibility for preview comparison and updates.
25
+ if (prerelease(installed) === null) {
26
+ output.stdout(`${installed}\n`);
27
+ return 0;
28
+ }
23
29
  const remote = await queryDistTags(runner, options.fetcher ?? defaultRegistryFetcher);
24
30
  output.stdout(`Current: ${installed}\nDevelop: ${remote.develop ?? "unavailable"}\nRelease: ${remote.release ?? "unavailable"}\n`);
25
31
  if (remote.error)
@@ -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,17 +13,20 @@ 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(),
21
- ...(options.ownedSurfaces === "off" ? {} : { hiddenAgentSettingIds: ["tuiMode"] }),
22
+ ...(ownedSurfaces ? {
23
+ hiddenAgentSettingIds: ["tuiMode", "theme", "fullscreenScrollbar", "quietStartup"],
24
+ } : {}),
22
25
  });
23
- // Before anything is drawn, so every surface uses the configured theme rather
24
- // than the one guessed from the terminal's background.
25
- applyConfiguredPiTheme(adapter.configuredTheme());
26
- const ownedSurfaces = options.ownedSurfaces !== "off";
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());
27
30
  const routeHost = settings === null || !ownedSurfaces ? null : createOwnedRouteHost(settings);
28
31
  const viewportSettings = settings === null || !ownedSurfaces ? null : {
29
32
  snapshot: () => viewportSettingsSnapshot(settings),
@@ -51,7 +54,7 @@ function viewportSettingsSnapshot(settings) {
51
54
  const style = settings.value("scrollbarStyle");
52
55
  const speed = settings.value("scrollbarSpeed");
53
56
  return {
54
- scrollbarAppearance: appearance === "always" || appearance === "hidden" ? appearance : "hover",
57
+ scrollbarAppearance: appearance === "always" || appearance === "hidden" ? appearance : "auto",
55
58
  scrollbarStyle: style === "thick" ? "thick" : "thin",
56
59
  scrollbarSpeed: speed === "high" ? "high" : speed === "fast" ? "fast" : "normal",
57
60
  };
@@ -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
@@ -13,7 +13,7 @@ export interface OwnedUiModelInfo {
13
13
  }
14
14
  export type OwnedUiThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
15
15
  export interface OwnedUiViewportSettings {
16
- readonly scrollbarAppearance: "always" | "hover" | "hidden";
16
+ readonly scrollbarAppearance: "auto" | "always" | "hidden";
17
17
  readonly scrollbarStyle: "thin" | "thick";
18
18
  /** normal is baseline; fast is 2x normal; high is normal plus fast. */
19
19
  readonly scrollbarSpeed: "normal" | "fast" | "high";
@@ -29,6 +29,11 @@ export interface OwnedUiTerminalSurface {
29
29
  readonly focusedRegion: "transcript" | "editor" | "dialog" | "overlay" | "status";
30
30
  readonly hardwareCursor: boolean;
31
31
  }
32
+ export interface OwnedUiImageAttachment {
33
+ readonly type: "image";
34
+ readonly data: string;
35
+ readonly mimeType: string;
36
+ }
32
37
  export interface OwnedUiEditorState {
33
38
  readonly text: string;
34
39
  readonly queuedSubmissions: readonly string[];
@@ -133,11 +138,13 @@ export type OwnedUiCommand = {
133
138
  readonly correlationId: OwnedUiCorrelationId;
134
139
  readonly sessionId: OwnedUiSessionId;
135
140
  readonly text: string;
141
+ readonly images?: readonly OwnedUiImageAttachment[];
136
142
  } | {
137
143
  readonly type: "steer" | "follow-up";
138
144
  readonly correlationId: OwnedUiCorrelationId;
139
145
  readonly sessionId: OwnedUiSessionId;
140
146
  readonly text: string;
147
+ readonly images?: readonly OwnedUiImageAttachment[];
141
148
  } | {
142
149
  readonly type: "abort" | "retry" | "compact" | "shutdown";
143
150
  readonly correlationId: OwnedUiCorrelationId;
@@ -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":
@@ -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",
@@ -0,0 +1,55 @@
1
+ import { type Editor } from "#pi-tui";
2
+ import type { KeybindingsManager } from "./upstream/adjacent/core/keybindings.js";
3
+ import type { PiShellClipboardContent, PiShellEditorTextRange } from "./shell-shared-facade.js";
4
+ /**
5
+ * Middleware boundary around Pi's editor. Interceptors may claim an input or
6
+ * delegate to the next interceptor/vanilla editor, and may decorate the rows
7
+ * vanilla Pi renders. This keeps owned UX additions out of the synchronized
8
+ * Pi editor port and gives future interactions one registration point.
9
+ */
10
+ export interface OwnedEditorUxInterceptor {
11
+ handleInput(data: string, next: () => void): void;
12
+ render(width: number, next: () => string[]): string[];
13
+ reset(): void;
14
+ handlePointer?(event: OwnedEditorPointerEvent): boolean;
15
+ hasSelection?(): boolean;
16
+ ownsPointer?(): boolean;
17
+ pasteClipboard?(): boolean;
18
+ }
19
+ export interface OwnedEditorPointerEvent {
20
+ readonly kind: "press" | "motion" | "release";
21
+ readonly button: number;
22
+ readonly column: number;
23
+ /** One-based row relative to the top of the editor component. */
24
+ readonly row: number;
25
+ }
26
+ export declare class OwnedEditorUxInterception {
27
+ readonly interceptors: readonly OwnedEditorUxInterceptor[];
28
+ readonly fallback: {
29
+ handleInput(data: string): void;
30
+ render(width: number): string[];
31
+ };
32
+ constructor(interceptors: readonly OwnedEditorUxInterceptor[], fallback: {
33
+ handleInput(data: string): void;
34
+ render(width: number): string[];
35
+ });
36
+ handleInput(data: string): void;
37
+ render(width: number): string[];
38
+ reset(): void;
39
+ handlePointer(event: OwnedEditorPointerEvent): boolean;
40
+ hasSelection(): boolean;
41
+ ownsPointer(): boolean;
42
+ pasteClipboard(): boolean;
43
+ }
44
+ export interface PromptSelectionUxOptions {
45
+ readonly copyText: (text: string) => void;
46
+ readonly readClipboardContent: () => Promise<PiShellClipboardContent | null>;
47
+ readonly transformPastedContent: (content: PiShellClipboardContent) => string;
48
+ readonly atomicRanges: (line: string) => readonly PiShellEditorTextRange[];
49
+ readonly expandCopiedText: (text: string) => string;
50
+ readonly paintSelection: (line: string, from: number, to: number, atomic: boolean) => string;
51
+ readonly decorateRow: (row: string, width: number) => string;
52
+ readonly requestRender: () => void;
53
+ readonly getRows: () => number;
54
+ }
55
+ export declare function createPromptSelectionInterceptor(editor: Editor, keybindings: KeybindingsManager, options: PromptSelectionUxOptions): OwnedEditorUxInterceptor;