@timurproko/a1 0.1.8-dev.137 → 0.1.8-dev.139
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -4
- package/dist/cli/capabilities.d.ts +2 -11
- package/dist/cli/capabilities.js +1 -1
- package/dist/cli/dispatch.js +3 -6
- package/dist/composition/owned-ui.js +19 -1
- package/dist/contracts/owned-ui/model.d.ts +21 -0
- package/dist/contracts/owned-ui/validation.js +3 -0
- package/dist/features/launch/development-launch.js +11 -3
- package/dist/features/launch/index.d.ts +0 -1
- package/dist/features/launch/index.js +0 -1
- package/dist/features/launch/intent.d.ts +3 -2
- package/dist/features/launch/intent.js +1 -2
- package/dist/features/launch/prepare-launch.d.ts +0 -2
- package/dist/features/launch/prepare-launch.js +2 -9
- package/dist/features/launch/profile-paths.d.ts +1 -2
- package/dist/features/launch/profile-paths.js +2 -8
- package/dist/features/launch/runtime-selection.d.ts +2 -5
- package/dist/features/owned-ui/settings-app.js +36 -13
- package/dist/foundation/lifecycle/model.d.ts +1 -1
- package/dist/foundation/lifecycle/model.js +1 -1
- package/dist/foundation/release/bootstrap.d.ts +1 -3
- package/dist/foundation/release/bootstrap.js +1 -1
- package/dist/foundation/storage/control-store.js +37 -3
- package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
- package/dist/integrations/pi/components/shell-footer-status.js +14 -5
- package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -2
- package/dist/integrations/pi/components/shell-presenters-transcript.js +9 -8
- package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +13 -0
- package/dist/integrations/pi/components/submitted-prompt-adapter.js +27 -0
- package/dist/integrations/pi/engine/adapter.js +16 -2
- package/dist/integrations/pi/engine/session-integration.js +5 -2
- package/dist/integrations/pi/owned-ui/session-shell-root.d.ts +17 -2
- package/dist/integrations/pi/owned-ui/session-shell-root.js +451 -42
- package/dist/integrations/pi/owned-ui/session-shell.js +116 -13
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -1
- package/dist/integrations/pi/tui-runtime/adapter.js +51 -2
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +2 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +3 -3
- package/dist/native/linux-x64/process-guardian +0 -0
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/product-identity.d.ts +2 -2
- package/dist/product-identity.js +3 -4
- package/dist/product-identity.json +3 -5
- package/dist/ui/components/index.d.ts +3 -0
- package/dist/ui/components/index.js +3 -0
- package/dist/ui/components/mouse.d.ts +10 -0
- package/dist/ui/components/mouse.js +23 -0
- package/dist/ui/components/prompt-row.d.ts +14 -0
- package/dist/ui/components/prompt-row.js +20 -0
- package/dist/ui/components/scrollbar.d.ts +26 -0
- package/dist/ui/components/scrollbar.js +35 -0
- package/dist/ui/components/spans.d.ts +6 -0
- package/dist/ui/components/spans.js +36 -0
- package/dist/ui/components/submitted-prompt.d.ts +23 -0
- package/dist/ui/components/submitted-prompt.js +50 -0
- package/dist/ui/components/text-selection.d.ts +53 -0
- package/dist/ui/components/text-selection.js +168 -0
- package/dist/ui/components/text.d.ts +14 -0
- package/dist/ui/components/text.js +40 -0
- package/dist/ui/components/transcript-viewport.d.ts +98 -0
- package/dist/ui/components/transcript-viewport.js +353 -0
- package/dist/ui/settings/declarations.d.ts +8 -1
- package/dist/ui/settings/declarations.js +33 -5
- package/dist/ui/settings/migrations.d.ts +1 -1
- package/dist/ui/settings/migrations.js +12 -2
- package/dist/ui/settings/sections.js +15 -8
- package/dist/ui/settings/session.d.ts +2 -0
- package/dist/ui/settings/session.js +7 -5
- package/docs/architecture/boundaries.md +2 -2
- package/docs/architecture/project-structure.md +1 -1
- package/docs/architecture/resource-and-data-policy.md +1 -1
- package/docs/architecture/toolchain.md +5 -5
- package/docs/architecture/ui-reference-provenance.md +11 -2
- package/docs/features/launch-profiles.md +7 -14
- package/docs/manual-launch-instance-acceptance.md +12 -35
- package/docs/manual-owned-ui-checkpoint.md +2 -3
- package/docs/manual-pi-boundary-candidate.md +4 -5
- package/package.json +1 -2
- package/dist/features/launch/profiles.d.ts +0 -12
- package/dist/features/launch/profiles.js +0 -26
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
|
|
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`
|
|
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
|
|
3
|
+
readonly developmentComparison: boolean;
|
|
13
4
|
}
|
|
14
5
|
export declare function cliCapabilities(version: string): CliCapabilities;
|
|
15
6
|
export declare function isPrereleaseVersion(version: string): boolean;
|
package/dist/cli/capabilities.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { prerelease } from "semver";
|
|
2
2
|
export function cliCapabilities(version) {
|
|
3
|
-
return Object.freeze({
|
|
3
|
+
return Object.freeze({ developmentComparison: isPrereleaseVersion(version) });
|
|
4
4
|
}
|
|
5
5
|
export function isPrereleaseVersion(version) {
|
|
6
6
|
return (prerelease(version)?.length ?? 0) > 0;
|
package/dist/cli/dispatch.js
CHANGED
|
@@ -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.
|
|
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"
|
|
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.
|
|
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:"))
|
|
@@ -18,16 +18,24 @@ export async function composeOwnedUi(options = {}) {
|
|
|
18
18
|
: new OwnedUiSettingsSession({
|
|
19
19
|
store: new OwnedUiSettingsStore({ configDir: resolveProductPaths().configDir, profileId: options.profileId }),
|
|
20
20
|
agentProvider: () => adapter.settingsPort(),
|
|
21
|
+
...(options.ownedSurfaces === "off" ? {} : { hiddenAgentSettingIds: ["tuiMode"] }),
|
|
21
22
|
});
|
|
22
23
|
// Before anything is drawn, so every surface uses the configured theme rather
|
|
23
24
|
// than the one guessed from the terminal's background.
|
|
24
25
|
applyConfiguredPiTheme(adapter.configuredTheme());
|
|
25
|
-
const
|
|
26
|
+
const ownedSurfaces = options.ownedSurfaces !== "off";
|
|
27
|
+
const routeHost = settings === null || !ownedSurfaces ? null : createOwnedRouteHost(settings);
|
|
28
|
+
const viewportSettings = settings === null || !ownedSurfaces ? null : {
|
|
29
|
+
snapshot: () => viewportSettingsSnapshot(settings),
|
|
30
|
+
onChange: listener => settings.onChange(() => listener(viewportSettingsSnapshot(settings))),
|
|
31
|
+
};
|
|
26
32
|
const shell = new OwnedUiSessionShell({
|
|
27
33
|
backend: adapter,
|
|
28
34
|
cwd,
|
|
29
35
|
...(options.terminal === undefined ? {} : { terminal: createPiTerminalBridge(options.terminal) }),
|
|
30
36
|
...(routeHost === null ? {} : { routeHost }),
|
|
37
|
+
...(ownedSurfaces ? { sessionLayout: "custom-viewport" } : {}),
|
|
38
|
+
...(viewportSettings === null ? {} : { viewportSettings }),
|
|
31
39
|
});
|
|
32
40
|
const application = {
|
|
33
41
|
get disposed() { return adapter.disposed; },
|
|
@@ -38,3 +46,13 @@ export async function composeOwnedUi(options = {}) {
|
|
|
38
46
|
};
|
|
39
47
|
return { application, settings };
|
|
40
48
|
}
|
|
49
|
+
function viewportSettingsSnapshot(settings) {
|
|
50
|
+
const appearance = settings.value("scrollbarAppearance");
|
|
51
|
+
const style = settings.value("scrollbarStyle");
|
|
52
|
+
const speed = settings.value("scrollbarSpeed");
|
|
53
|
+
return {
|
|
54
|
+
scrollbarAppearance: appearance === "always" || appearance === "hidden" ? appearance : "hover",
|
|
55
|
+
scrollbarStyle: style === "thick" ? "thick" : "thin",
|
|
56
|
+
scrollbarSpeed: speed === "high" ? "high" : speed === "fast" ? "fast" : "normal",
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -12,6 +12,17 @@ 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: "always" | "hover" | "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;
|
|
@@ -189,6 +200,16 @@ export type OwnedUiEvent = {
|
|
|
189
200
|
readonly sessionId: OwnedUiSessionId;
|
|
190
201
|
readonly sequence: number;
|
|
191
202
|
readonly block: OwnedUiTranscriptBlock;
|
|
203
|
+
} | {
|
|
204
|
+
/** One completed assistant reply; never a tool row or transcript rebuild. */
|
|
205
|
+
readonly type: "assistant-message-completed";
|
|
206
|
+
readonly sessionId: OwnedUiSessionId;
|
|
207
|
+
readonly sequence: number;
|
|
208
|
+
} | {
|
|
209
|
+
/** A fresh agent run has started, used by follow-mode surfaces. */
|
|
210
|
+
readonly type: "agent-run-started";
|
|
211
|
+
readonly sessionId: OwnedUiSessionId;
|
|
212
|
+
readonly sequence: number;
|
|
192
213
|
} | {
|
|
193
214
|
readonly type: "editor-state";
|
|
194
215
|
readonly sessionId: OwnedUiSessionId;
|
|
@@ -103,6 +103,9 @@ export function assertOwnedUiEvent(event) {
|
|
|
103
103
|
case "transcript-block":
|
|
104
104
|
assertOwnedUiTranscriptBlock(event.block);
|
|
105
105
|
return;
|
|
106
|
+
case "assistant-message-completed":
|
|
107
|
+
case "agent-run-started":
|
|
108
|
+
return;
|
|
106
109
|
case "editor-state":
|
|
107
110
|
assertOwnedUiEditorState(event.editor);
|
|
108
111
|
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
|
|
12
|
-
|
|
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] ??
|
|
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)),
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import
|
|
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
|
|
5
|
+
readonly profileId: LaunchProfileId;
|
|
5
6
|
}
|
|
6
7
|
export type LaunchIntent = InteractiveLaunchIntent;
|
|
7
8
|
export declare function interactiveLaunchIntent(profileId: LaunchProfileId): InteractiveLaunchIntent;
|
|
@@ -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.
|
|
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
|
-
|
|
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 "./
|
|
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
|
-
|
|
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
|
-
*
|
|
4
|
-
*
|
|
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 {
|
|
@@ -56,6 +56,8 @@ export class SettingsApp {
|
|
|
56
56
|
#session;
|
|
57
57
|
#selectedKey;
|
|
58
58
|
#scroll = 0;
|
|
59
|
+
/** Keyboard navigation requests visibility once; pointer scrolling then stays free. */
|
|
60
|
+
#selectionNeedsReveal = true;
|
|
59
61
|
#reveal;
|
|
60
62
|
#notice = null;
|
|
61
63
|
#filter = null;
|
|
@@ -96,9 +98,13 @@ export class SettingsApp {
|
|
|
96
98
|
this.#panelTop = this.#panelTopForFrame;
|
|
97
99
|
const bodyHeight = Math.max(0, rect.height - footer.length);
|
|
98
100
|
const selected = indexOfKey(rows, this.#selectedKey);
|
|
99
|
-
|
|
101
|
+
if (this.#selectionNeedsReveal) {
|
|
102
|
+
this.#scroll = scrollForSelection(rows, bodyHeight, this.#scroll, selected, this.#reveal);
|
|
103
|
+
this.#selectionNeedsReveal = false;
|
|
104
|
+
}
|
|
100
105
|
this.#reveal = undefined;
|
|
101
106
|
const layout = layoutList(rows, bodyHeight, this.#scroll);
|
|
107
|
+
this.#scroll = layout.scroll;
|
|
102
108
|
const contentWidth = Math.max(0, rect.width - RAIL_COLUMNS);
|
|
103
109
|
const valueColumn = this.#valueColumn(rows);
|
|
104
110
|
const geometry = scrollbarGeometry({
|
|
@@ -121,14 +127,13 @@ export class SettingsApp {
|
|
|
121
127
|
for (const index of layout.rowIndexes) {
|
|
122
128
|
const row = rows[index];
|
|
123
129
|
if (row !== undefined && row.kind === "element") {
|
|
130
|
+
const view = this.#viewRow(row.value);
|
|
124
131
|
this.#frameRows.push({
|
|
125
|
-
key:
|
|
132
|
+
key: view.key,
|
|
126
133
|
screenRow: body.length,
|
|
127
134
|
valueColumn,
|
|
128
|
-
valueWidth:
|
|
129
|
-
|
|
130
|
-
: row.value.value === null ? 0 : displayWidth(displayValue(row.value.value)),
|
|
131
|
-
stepper: isStepper(row.value),
|
|
135
|
+
valueWidth: displayWidth(view.value),
|
|
136
|
+
stepper: view.stepper !== undefined,
|
|
132
137
|
});
|
|
133
138
|
}
|
|
134
139
|
body.push(this.#renderRow(row, index === selected, contentWidth, valueColumn, theme));
|
|
@@ -189,8 +194,11 @@ export class SettingsApp {
|
|
|
189
194
|
// End lands on the very last setting, not on the head of its section.
|
|
190
195
|
const selectable = selectableIndexes(rows);
|
|
191
196
|
const target = action === "last" ? selectable.at(-1) : selectable[0];
|
|
192
|
-
if (target !== undefined)
|
|
193
|
-
this.#
|
|
197
|
+
if (target !== undefined) {
|
|
198
|
+
this.#select(rows, target);
|
|
199
|
+
if (action === "first")
|
|
200
|
+
this.#scroll = 0;
|
|
201
|
+
}
|
|
194
202
|
return { consumed: true };
|
|
195
203
|
}
|
|
196
204
|
case "previous-value":
|
|
@@ -263,6 +271,14 @@ export class SettingsApp {
|
|
|
263
271
|
}
|
|
264
272
|
return { consumed: true, render: false };
|
|
265
273
|
}
|
|
274
|
+
if (event.kind === "wheel-up" || event.kind === "wheel-down") {
|
|
275
|
+
// The whole list pane owns wheel scrolling, including blank space beside
|
|
276
|
+
// short labels. It must not depend on finding an item under the pointer.
|
|
277
|
+
if (event.row < 1 || event.row > this.#panelTopForFrame)
|
|
278
|
+
return { consumed: false };
|
|
279
|
+
this.#scroll = Math.max(0, this.#scroll + (event.kind === "wheel-down" ? 3 : -3));
|
|
280
|
+
return { consumed: true };
|
|
281
|
+
}
|
|
266
282
|
const row = this.#frameRows.find(candidate => candidate.screenRow === event.row - 1);
|
|
267
283
|
const previousKey = this.#hoverKey;
|
|
268
284
|
const previousRegion = this.#hoverRegion;
|
|
@@ -288,10 +304,6 @@ export class SettingsApp {
|
|
|
288
304
|
}
|
|
289
305
|
return { consumed: true };
|
|
290
306
|
}
|
|
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
307
|
const changed = previousKey !== this.#hoverKey || previousRegion !== this.#hoverRegion;
|
|
296
308
|
return { consumed: event.kind !== "motion", render: changed };
|
|
297
309
|
}
|
|
@@ -403,6 +415,17 @@ export class SettingsApp {
|
|
|
403
415
|
if (input === null)
|
|
404
416
|
return { consumed: false };
|
|
405
417
|
const key = KEYS[data];
|
|
418
|
+
if (key === "home" || key === "end") {
|
|
419
|
+
const rows = this.#rows();
|
|
420
|
+
const selectable = selectableIndexes(rows);
|
|
421
|
+
const target = key === "end" ? selectable.at(-1) : selectable[0];
|
|
422
|
+
if (target !== undefined) {
|
|
423
|
+
this.#select(rows, target);
|
|
424
|
+
if (key === "home")
|
|
425
|
+
this.#scroll = 0;
|
|
426
|
+
}
|
|
427
|
+
return { consumed: true };
|
|
428
|
+
}
|
|
406
429
|
if (key === "up" || key === "down" || key === "shift+up" || key === "shift+down") {
|
|
407
430
|
const rows = this.#rows();
|
|
408
431
|
// Nothing found means nothing to move through; the key is still swallowed
|
|
@@ -485,6 +508,7 @@ export class SettingsApp {
|
|
|
485
508
|
if (index < 0)
|
|
486
509
|
return;
|
|
487
510
|
this.#selectedKey = rowKey(rows[index]);
|
|
511
|
+
this.#selectionNeedsReveal = true;
|
|
488
512
|
this.#notice = null;
|
|
489
513
|
}
|
|
490
514
|
#rows() {
|
|
@@ -554,7 +578,6 @@ export class SettingsApp {
|
|
|
554
578
|
key: `${entry.backend}:${entry.id}`,
|
|
555
579
|
label: labelOf(entry),
|
|
556
580
|
value,
|
|
557
|
-
...(entry.origin === "default" ? { suffix: "(default)" } : {}),
|
|
558
581
|
...(typeof shown === "number" && entry.editable ? { stepper: stepperEnds(range, shown) } : {}),
|
|
559
582
|
};
|
|
560
583
|
}
|
|
@@ -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"
|
|
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
|
|
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?.
|
|
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 >
|
|
34
|
-
throw new Error(`control database version ${version} is newer than supported version
|
|
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'
|
|
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) {
|
|
@@ -4,4 +4,4 @@ export declare function createPiShellHeader(options?: PiShellHeaderOptions): PiS
|
|
|
4
4
|
export declare function createPiShellLoadedResources(resources: readonly PiShellResourceEntry[], initialExpanded?: boolean): PiShellLoadedResourcesPort;
|
|
5
5
|
export declare function createPiShellStatus(view: OwnedUiSessionViewModel, runtime?: Pick<PiShellEditorOptions, "getColumns" | "getRows" | "requestRender">): PiShellStatusPort;
|
|
6
6
|
export declare function createPiShellFooter(view: OwnedUiSessionViewModel, cwd: string): PiShellViewComponentPort;
|
|
7
|
-
export declare function createPiQueuedInputStatus(submissions: readonly string[]): PiShellQueuedInputPort;
|
|
7
|
+
export declare function createPiQueuedInputStatus(submissions: readonly string[], presentation?: "pinned" | "custom-viewport"): PiShellQueuedInputPort;
|
|
@@ -110,14 +110,14 @@ export function createPiShellFooter(view, cwd) {
|
|
|
110
110
|
dispose: () => footer.dispose(),
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
|
-
export function createPiQueuedInputStatus(submissions) {
|
|
114
|
-
const text = new Text(queuedInputText(submissions), 1, 0);
|
|
113
|
+
export function createPiQueuedInputStatus(submissions, presentation = "pinned") {
|
|
114
|
+
const text = new Text(queuedInputText(submissions, presentation), 1, 0);
|
|
115
115
|
return {
|
|
116
116
|
render: width => submissions.length === 0 ? [] : text.render(width),
|
|
117
117
|
invalidate: () => text.invalidate(),
|
|
118
118
|
update(next) {
|
|
119
119
|
submissions = next;
|
|
120
|
-
text.setText(queuedInputText(next));
|
|
120
|
+
text.setText(queuedInputText(next, presentation));
|
|
121
121
|
},
|
|
122
122
|
};
|
|
123
123
|
}
|
|
@@ -135,9 +135,18 @@ function statusComponent(view, ui, workingOverride) {
|
|
|
135
135
|
function statusSignature(view, workingOverride) {
|
|
136
136
|
return `${view.lifecycle}\u0000${workingOverride ?? ""}\u0000${view.status.workingMessage ?? ""}\u0000${view.status.diagnostics.at(-1) ?? ""}`;
|
|
137
137
|
}
|
|
138
|
-
function queuedInputText(submissions) {
|
|
138
|
+
function queuedInputText(submissions, presentation) {
|
|
139
|
+
if (submissions.length === 0)
|
|
140
|
+
return "";
|
|
139
141
|
const theme = piTheme();
|
|
140
|
-
|
|
142
|
+
if (presentation === "pinned") {
|
|
143
|
+
return submissions.map(submission => theme.fg("muted", `Steering: ${submission.replaceAll("\n", " ⏎ ")}`)).join("\n");
|
|
144
|
+
}
|
|
145
|
+
const messages = submissions.map(submission => theme.fg("dim", `Steering: ${submission.replaceAll("\n", " ⏎ ")}`));
|
|
146
|
+
const dequeueHint = theme.fg("dim", "↳ Alt+Up to edit all queued messages");
|
|
147
|
+
// The custom viewport matches Pi's interactive queue presentation while the
|
|
148
|
+
// comparison shell remains byte-for-byte compatible with its pinned fixture.
|
|
149
|
+
return ["", ...messages, dequeueHint].join("\n");
|
|
141
150
|
}
|
|
142
151
|
function compactHeaderText() {
|
|
143
152
|
const theme = piTheme();
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { AssistantMessageComponent } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { OwnedUiTranscriptBlock } from "../../../contracts/owned-ui/index.js";
|
|
3
|
+
import { type PiShellSubmittedPromptComposer } from "./submitted-prompt-adapter.js";
|
|
4
|
+
export type { PiShellSubmittedPromptComposer } from "./submitted-prompt-adapter.js";
|
|
3
5
|
import { type PiShellComponentPort, type PiShellExtensionRendererResolver, type PiShellTranscriptComponentPort } from "./shell-shared-facade.js";
|
|
4
6
|
export interface PiShellSessionInfoPresentation {
|
|
5
7
|
readonly sessionName?: string;
|
|
@@ -35,7 +37,7 @@ export declare function renderPiShellStatusText(message: string, width: number):
|
|
|
35
37
|
export declare function createPiShellSessionInfo(presentation: PiShellSessionInfoPresentation): PiShellComponentPort;
|
|
36
38
|
export declare function createPiShellChangelog(markdown: string): PiShellComponentPort;
|
|
37
39
|
export declare function createPiShellHotkeys(): PiShellComponentPort;
|
|
38
|
-
export declare function createPiShellTranscriptComponent(initial: OwnedUiTranscriptBlock, cwd: string, extensions?: PiShellExtensionRendererResolver): PiShellTranscriptComponentPort;
|
|
40
|
+
export declare function createPiShellTranscriptComponent(initial: OwnedUiTranscriptBlock, cwd: string, extensions?: PiShellExtensionRendererResolver, submittedPrompt?: PiShellSubmittedPromptComposer): PiShellTranscriptComponentPort;
|
|
39
41
|
export declare function renderPiShellTranscriptBlock(block: OwnedUiTranscriptBlock, width: number, cwd: string): readonly string[];
|
|
40
42
|
/**
|
|
41
43
|
* Pinned Pi's CLI prints startup diagnostics with `reportDiagnostics` before
|
|
@@ -54,4 +56,3 @@ export declare function renderPiShellStartupDiagnostic(diagnostic: {
|
|
|
54
56
|
export declare function renderPiShellPackageUpdateNotice(packages: readonly string[], width: number): readonly string[];
|
|
55
57
|
type PiAssistantMessage = NonNullable<ConstructorParameters<typeof AssistantMessageComponent>[0]>;
|
|
56
58
|
export declare function validatedAssistantMessage(block: OwnedUiTranscriptBlock): PiAssistantMessage;
|
|
57
|
-
export {};
|