@timurproko/a1 0.1.8-dev.151 → 0.1.8-dev.182
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 +53 -9
- package/bin/cli.js +1 -0
- package/bin/ui.js +17 -2
- package/dist/cli/dispatch.d.ts +6 -0
- package/dist/cli/dispatch.js +107 -81
- package/dist/cli/version-stats.js +7 -1
- package/dist/composition/owned-ui.d.ts +4 -1
- package/dist/composition/owned-ui.js +8 -5
- package/dist/contracts/agent-engine/capability-ports.d.ts +2 -2
- package/dist/contracts/agent-engine/domain-validation.js +22 -4
- package/dist/contracts/agent-engine/domain.d.ts +17 -0
- package/dist/contracts/owned-ui/model.d.ts +8 -0
- package/dist/contracts/owned-ui/validation.js +11 -0
- package/dist/features/owned-ui/index.d.ts +1 -0
- package/dist/features/owned-ui/index.js +1 -0
- package/dist/features/owned-ui/project-trust-prompt.d.ts +20 -0
- package/dist/features/owned-ui/project-trust-prompt.js +32 -0
- package/dist/features/owned-ui/settings-app.js +37 -9
- package/dist/integrations/pi/components/shell-components.d.ts +1 -0
- package/dist/integrations/pi/components/shell-components.js +1 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +10 -0
- package/dist/integrations/pi/components/shell-footer-status.js +14 -9
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +36 -0
- package/dist/integrations/pi/components/shell-presenters-info.js +78 -0
- package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -36
- package/dist/integrations/pi/components/shell-presenters-transcript.js +88 -128
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +11 -1
- package/dist/integrations/pi/engine/adapter.d.ts +15 -4
- package/dist/integrations/pi/engine/adapter.js +185 -37
- package/dist/integrations/pi/engine/http-dispatcher.d.ts +4 -0
- package/dist/integrations/pi/engine/http-dispatcher.js +25 -0
- package/dist/integrations/pi/engine/index.d.ts +3 -0
- package/dist/integrations/pi/engine/index.js +3 -0
- package/dist/integrations/pi/engine/project-trust-preflight.d.ts +22 -0
- package/dist/integrations/pi/engine/project-trust-preflight.js +50 -0
- package/dist/integrations/pi/engine/runtime-integration.d.ts +16 -1
- package/dist/integrations/pi/engine/runtime-integration.js +34 -4
- package/dist/integrations/pi/engine/settings-effects.d.ts +55 -0
- package/dist/integrations/pi/engine/settings-effects.js +229 -0
- package/dist/integrations/pi/engine/settings-integration.d.ts +14 -26
- package/dist/integrations/pi/engine/settings-integration.js +102 -108
- package/dist/integrations/pi/engine/workflow-controllers.d.ts +1 -1
- package/dist/integrations/pi/session-ui/clipboard-image.d.ts +11 -0
- package/dist/integrations/pi/session-ui/clipboard-image.js +29 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +5 -1
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +9 -2
- package/dist/integrations/pi/session-ui/session-shell-root.js +83 -9
- package/dist/integrations/pi/session-ui/session-shell.d.ts +2 -0
- package/dist/integrations/pi/session-ui/session-shell.js +151 -15
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +34 -2
- package/dist/integrations/pi/session-ui/system-clipboard.d.ts +9 -0
- package/dist/integrations/pi/session-ui/system-clipboard.js +34 -5
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +4 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +28 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/components/spans.d.ts +2 -0
- package/dist/ui/components/spans.js +25 -0
- package/dist/ui/components/text.js +24 -2
- package/dist/ui/settings/sections.d.ts +8 -19
- package/dist/ui/settings/sections.js +12 -4
- package/dist/ui/settings/session.d.ts +8 -18
- package/dist/ui/settings/session.js +59 -52
- package/docs/architecture/project-structure.md +14 -0
- package/docs/ci-release-runbook.md +75 -9
- package/docs/features/launch-profiles.md +3 -3
- package/docs/repository-governance-live-acceptance.md +77 -0
- package/package.json +5 -2
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
|
|
27
|
-
a1
|
|
28
|
-
a1
|
|
29
|
-
a1 update
|
|
30
|
-
a1 update
|
|
31
|
-
a1 update --
|
|
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
|
|
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`.
|
|
@@ -66,6 +78,38 @@ npm run test:fast # typecheck + fast suite (alias: npm test)
|
|
|
66
78
|
npm run test:full # complete non-physical suite
|
|
67
79
|
```
|
|
68
80
|
|
|
81
|
+
Create every task worktree at `{working-dir}/.worktrees/<task-id>`, where
|
|
82
|
+
`{working-dir}` is the session's initial working directory. For working directory
|
|
83
|
+
`D:/Git/a1`, use `D:/Git/a1/.worktrees/<task-id>`—never a sibling such as
|
|
84
|
+
`D:/Git/a1-<task-id>`. The primary worktree remains on `develop` for integration only.
|
|
85
|
+
|
|
86
|
+
## Pull request integration
|
|
87
|
+
|
|
88
|
+
Pull requests whose complete diff is only under `openspec/**`, under `docs/**`,
|
|
89
|
+
exactly the root `README.md`, or a combination of those paths are automatically
|
|
90
|
+
squash-merged after `Development validation required` succeeds. The automation
|
|
91
|
+
reads the complete GitHub changed-file list, including both sides of renames, and
|
|
92
|
+
runs only for trusted branches in this repository.
|
|
93
|
+
|
|
94
|
+
Any other path makes the pull request code/operational. That includes source, tests,
|
|
95
|
+
scripts, workflows, configuration, generated baselines, arbitrary root Markdown,
|
|
96
|
+
and a mixed documentation-plus-code change. Those pull requests remain open after
|
|
97
|
+
CI for local maintainer validation and explicit manual merge; automation disables
|
|
98
|
+
auto-merge if it was armed. Documentation remains exempt from product builds and
|
|
99
|
+
tests, but docs-sensitive generated governance and strict OpenSpec consistency are
|
|
100
|
+
checked before integration.
|
|
101
|
+
|
|
102
|
+
After any same-repository pull request into `develop` merges, trusted automation
|
|
103
|
+
reconciles its remote topic branch. Human merges use the close-event workflow;
|
|
104
|
+
documentation merges authored by `GITHUB_TOKEN` use a synchronous fallback because
|
|
105
|
+
GitHub suppresses recursive workflow events. Both delete only an unprotected live
|
|
106
|
+
ref that still equals the pull request's exact merged head SHA. Fork, advanced,
|
|
107
|
+
reserved, protected, malformed, and unmerged refs are preserved and reported.
|
|
108
|
+
|
|
109
|
+
Specification approval and implementation remain separate pull requests. An
|
|
110
|
+
implementation starts from updated `origin/develop` only after its specification
|
|
111
|
+
has merged and implementation was explicitly requested.
|
|
112
|
+
|
|
69
113
|
## Release
|
|
70
114
|
|
|
71
115
|
Two channels, both published by CI from the exact bytes it validated — never from
|
|
@@ -88,8 +132,8 @@ npm run develop # request the preview publish run and wait for it
|
|
|
88
132
|
Install:
|
|
89
133
|
|
|
90
134
|
```sh
|
|
91
|
-
a1 update
|
|
92
|
-
a1 update
|
|
135
|
+
a1 update --develop 107 # install preview 107
|
|
136
|
+
a1 update --develop 0.1.8-dev.107 # install that exact full preview version
|
|
93
137
|
```
|
|
94
138
|
|
|
95
139
|
### 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);
|
package/bin/ui.js
CHANGED
|
@@ -12,13 +12,28 @@ assertSinglePiTuiModuleAtLaunch(fileURLToPath(new URL("..", import.meta.url)), m
|
|
|
12
12
|
|
|
13
13
|
const { runSelectedInteractiveRuntime } = await import("../dist/features/launch/index.js");
|
|
14
14
|
|
|
15
|
+
const launchArgs = process.argv.slice(2);
|
|
16
|
+
let sessionPath;
|
|
17
|
+
if (launchArgs.length > 0) {
|
|
18
|
+
if (launchArgs.length !== 2 || launchArgs[0] !== "--session" || launchArgs[1].trim().length === 0) {
|
|
19
|
+
throw new Error("Usage: a1 [--session <session-file>]");
|
|
20
|
+
}
|
|
21
|
+
sessionPath = launchArgs[1];
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
runSelectedInteractiveRuntime(process.env.A1_LAUNCH_PROFILE ?? "a1", {
|
|
16
25
|
ownedUi: async (profileId, ownedSurfaces) => {
|
|
17
|
-
const [{ runOwnedUi }, { composeOwnedUi }] = await Promise.all([
|
|
26
|
+
const [{ createConsoleProjectTrustPrompt, runOwnedUi }, { composeOwnedUi }] = await Promise.all([
|
|
18
27
|
import("../dist/features/owned-ui/index.js"),
|
|
19
28
|
import("../dist/composition/index.js"),
|
|
20
29
|
]);
|
|
21
|
-
const { application, settings } = await composeOwnedUi({
|
|
30
|
+
const { application, settings } = await composeOwnedUi({
|
|
31
|
+
cwd: process.cwd(),
|
|
32
|
+
profileId,
|
|
33
|
+
ownedSurfaces,
|
|
34
|
+
projectTrustPrompt: createConsoleProjectTrustPrompt(),
|
|
35
|
+
...(sessionPath === undefined ? {} : { sessionPath }),
|
|
36
|
+
});
|
|
22
37
|
return await runOwnedUi({ application, ...(settings === null ? {} : { settings }) });
|
|
23
38
|
},
|
|
24
39
|
}).then(
|
package/dist/cli/dispatch.d.ts
CHANGED
|
@@ -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
|
} | {
|
package/dist/cli/dispatch.js
CHANGED
|
@@ -4,21 +4,60 @@ export function cliUsage(capabilities) {
|
|
|
4
4
|
return PRODUCT_TEXT.usage([
|
|
5
5
|
"",
|
|
6
6
|
...(capabilities.developmentComparison ? ["pi"] : []),
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return { kind: "
|
|
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 (
|
|
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
|
-
|
|
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: "
|
|
142
|
+
return { kind: "noop" };
|
|
117
143
|
}
|
|
118
144
|
function parsePiPackageUpdate(rest) {
|
|
119
|
-
if (rest.length === 0)
|
|
120
|
-
return
|
|
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: "
|
|
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,10 +1,12 @@
|
|
|
1
|
-
import { type PiEngineAdapter } from "../integrations/pi/engine/index.js";
|
|
1
|
+
import { type PiEngineAdapter, type PiProjectTrustPreflightPrompt } from "../integrations/pi/engine/index.js";
|
|
2
2
|
import { OwnedUiSettingsSession } from "../ui/settings/index.js";
|
|
3
3
|
import type { OwnedUiApplicationPort, PresentationTerminalPort } from "../contracts/presentation/index.js";
|
|
4
4
|
export interface OwnedUiCompositionOptions {
|
|
5
5
|
readonly cwd?: string;
|
|
6
6
|
readonly terminal?: PresentationTerminalPort;
|
|
7
7
|
readonly createPiAdapter?: () => Promise<PiEngineAdapter>;
|
|
8
|
+
/** Exact persisted session selected by the narrow `--session` launch form. */
|
|
9
|
+
readonly sessionPath?: string;
|
|
8
10
|
/**
|
|
9
11
|
* A1 profile whose settings this session reads and writes. Omitted keeps the
|
|
10
12
|
* session settings-free, which is what the pinned comparison paths use.
|
|
@@ -15,6 +17,7 @@ export interface OwnedUiCompositionOptions {
|
|
|
15
17
|
* use the same composition with those surfaces withheld.
|
|
16
18
|
*/
|
|
17
19
|
readonly ownedSurfaces?: "on" | "off";
|
|
20
|
+
readonly projectTrustPrompt?: PiProjectTrustPreflightPrompt;
|
|
18
21
|
}
|
|
19
22
|
export interface OwnedUiComposition {
|
|
20
23
|
readonly application: OwnedUiApplicationPort;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveProductPaths } from "../foundation/lifecycle/index.js";
|
|
2
2
|
import { applyConfiguredPiTheme, getAvailablePiThemes } from "../integrations/pi/components/index.js";
|
|
3
|
-
import { createPiEngineAdapter } from "../integrations/pi/engine/index.js";
|
|
3
|
+
import { createPiEngineAdapter, } from "../integrations/pi/engine/index.js";
|
|
4
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";
|
|
@@ -12,16 +12,19 @@ export async function composeOwnedUi(options = {}) {
|
|
|
12
12
|
const cwd = options.cwd ?? process.cwd();
|
|
13
13
|
const adapter = options.createPiAdapter
|
|
14
14
|
? await options.createPiAdapter()
|
|
15
|
-
: await createPiEngineAdapter({
|
|
15
|
+
: await createPiEngineAdapter({
|
|
16
|
+
cwd,
|
|
17
|
+
availableThemes: () => getAvailablePiThemes().map(theme => theme.name),
|
|
18
|
+
settingsProductMode: options.ownedSurfaces === "off" ? "comparison" : "bare",
|
|
19
|
+
...(options.sessionPath === undefined ? {} : { sessionPath: options.sessionPath }),
|
|
20
|
+
...(options.projectTrustPrompt === undefined ? {} : { projectTrustPrompt: options.projectTrustPrompt }),
|
|
21
|
+
});
|
|
16
22
|
const ownedSurfaces = options.ownedSurfaces !== "off";
|
|
17
23
|
const settings = options.profileId === undefined
|
|
18
24
|
? null
|
|
19
25
|
: new OwnedUiSettingsSession({
|
|
20
26
|
store: new OwnedUiSettingsStore({ configDir: resolveProductPaths().configDir, profileId: options.profileId }),
|
|
21
27
|
agentProvider: () => adapter.settingsPort(),
|
|
22
|
-
...(ownedSurfaces ? {
|
|
23
|
-
hiddenAgentSettingIds: ["tuiMode", "theme", "fullscreenScrollbar", "quietStartup"],
|
|
24
|
-
} : {}),
|
|
25
28
|
});
|
|
26
29
|
// Bare A1 intentionally ships one visual target while its UI is being completed:
|
|
27
30
|
// dark, regardless of terminal detection or a previously stored Pi theme. The
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentJsonValue, AgentModelDescriptor, AgentResourceDescriptor, AgentSettingDescriptor } from "./domain.js";
|
|
1
|
+
import type { AgentJsonValue, AgentModelDescriptor, AgentSettingChangeOutcome, AgentResourceDescriptor, AgentSettingDescriptor } from "./domain.js";
|
|
2
2
|
export interface AgentModelPort {
|
|
3
3
|
readonly capabilities: {
|
|
4
4
|
readonly selection: boolean;
|
|
@@ -26,7 +26,7 @@ export interface AgentSettingsPort {
|
|
|
26
26
|
};
|
|
27
27
|
listSettings(): Promise<readonly AgentSettingDescriptor[]>;
|
|
28
28
|
readSetting(key: string): Promise<AgentJsonValue | undefined>;
|
|
29
|
-
writeSetting?(key: string, value: AgentJsonValue): Promise<
|
|
29
|
+
writeSetting?(key: string, value: AgentJsonValue): Promise<AgentSettingChangeOutcome>;
|
|
30
30
|
flush?(): Promise<void>;
|
|
31
31
|
}
|
|
32
32
|
export interface AgentResourcesPort {
|
|
@@ -52,10 +52,28 @@ export function assertAgentUsage(value) { for (const amount of [value.inputToken
|
|
|
52
52
|
nonNegative(amount, "usage token count"); if (value.cost !== null && (!(typeof value.cost === "number") || !Number.isFinite(value.cost) || value.cost < 0))
|
|
53
53
|
throw new TypeError("usage cost is invalid"); }
|
|
54
54
|
export function assertAgentModelDescriptor(value) { id(value.providerId, "model provider id"); id(value.modelId, "model id"); text(value.displayName, "model display name"); nonNegative(value.contextWindow, "model context window"); unique(value.thinkingLevels, undefined, "model thinking levels"); }
|
|
55
|
-
export function assertAgentSettingDescriptor(value) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
export function assertAgentSettingDescriptor(value) {
|
|
56
|
+
id(value.key, "setting key");
|
|
57
|
+
if (!["boolean", "number", "string", "enum", "json"].includes(value.valueType)
|
|
58
|
+
|| typeof value.writable !== "boolean"
|
|
59
|
+
|| !["live", "next-session", "next-start", "current-exit"].includes(value.application)
|
|
60
|
+
|| !["agent", "shell", "terminal", "startup", "shutdown", "installation"].includes(value.owner)
|
|
61
|
+
|| typeof value.available !== "boolean"
|
|
62
|
+
|| !(value.limitationReason === null || typeof value.limitationReason === "string")) {
|
|
63
|
+
throw new TypeError("setting descriptor is invalid");
|
|
64
|
+
}
|
|
65
|
+
json(value.storedValue, "stored setting value");
|
|
66
|
+
json(value.effectiveValue, "effective setting value");
|
|
67
|
+
if ((value.available && value.limitationReason !== null)
|
|
68
|
+
|| (!value.available && (value.limitationReason === null || value.limitationReason.length === 0))) {
|
|
69
|
+
throw new TypeError("setting descriptor availability is contradictory");
|
|
70
|
+
}
|
|
71
|
+
if (value.writable && !value.available)
|
|
72
|
+
throw new TypeError("unavailable setting cannot be writable");
|
|
73
|
+
if (value.choices)
|
|
74
|
+
for (const choice of value.choices)
|
|
75
|
+
json(choice, "setting choice");
|
|
76
|
+
}
|
|
59
77
|
export function assertAgentResourceDescriptor(value) { id(value.id, "resource id"); if (!["command", "prompt", "skill", "extension", "other"].includes(value.kind))
|
|
60
78
|
throw new TypeError("resource kind is invalid"); text(value.label, "resource label"); json(value.metadata, "resource metadata"); }
|
|
61
79
|
export function assertAgentThemeDescriptor(value) { id(value.id, "theme id"); text(value.label, "theme label"); if (!value.tokens || typeof value.tokens !== "object" || Object.values(value.tokens).some(token => typeof token !== "string"))
|
|
@@ -48,10 +48,27 @@ export interface AgentModelDescriptor {
|
|
|
48
48
|
readonly contextWindow: number;
|
|
49
49
|
readonly thinkingLevels: readonly string[];
|
|
50
50
|
}
|
|
51
|
+
export type AgentSettingApplicationBoundary = "live" | "next-session" | "next-start" | "current-exit";
|
|
52
|
+
export type AgentSettingOwner = "agent" | "shell" | "terminal" | "startup" | "shutdown" | "installation";
|
|
53
|
+
export interface AgentSettingChangeOutcome {
|
|
54
|
+
readonly status: "applied" | "deferred" | "unavailable" | "failed";
|
|
55
|
+
readonly application: AgentSettingApplicationBoundary;
|
|
56
|
+
readonly storedValue: AgentJsonValue;
|
|
57
|
+
readonly effectiveValue: AgentJsonValue;
|
|
58
|
+
readonly failure: string | null;
|
|
59
|
+
readonly limitationReason: string | null;
|
|
60
|
+
}
|
|
51
61
|
export interface AgentSettingDescriptor {
|
|
52
62
|
readonly key: string;
|
|
53
63
|
readonly valueType: "boolean" | "number" | "string" | "enum" | "json";
|
|
64
|
+
/** A setting is writable only when its declared owner/effect is available. */
|
|
54
65
|
readonly writable: boolean;
|
|
66
|
+
readonly application: AgentSettingApplicationBoundary;
|
|
67
|
+
readonly owner: AgentSettingOwner;
|
|
68
|
+
readonly available: boolean;
|
|
69
|
+
readonly limitationReason: string | null;
|
|
70
|
+
readonly storedValue: AgentJsonValue;
|
|
71
|
+
readonly effectiveValue: AgentJsonValue;
|
|
55
72
|
readonly choices?: readonly AgentJsonValue[];
|
|
56
73
|
/** Label the engine shows for this setting, when it has one. */
|
|
57
74
|
readonly label?: string;
|
|
@@ -91,6 +91,12 @@ export interface OwnedUiOverlay {
|
|
|
91
91
|
readonly modal: boolean;
|
|
92
92
|
readonly payload: unknown;
|
|
93
93
|
}
|
|
94
|
+
export interface OwnedUiTranscriptImageReference {
|
|
95
|
+
readonly assetId: OwnedUiEntityId;
|
|
96
|
+
readonly mimeType: string;
|
|
97
|
+
readonly byteLength: number;
|
|
98
|
+
readonly source: "user" | "tool-result";
|
|
99
|
+
}
|
|
94
100
|
export interface OwnedUiTranscriptBlock {
|
|
95
101
|
readonly id: OwnedUiEntityId;
|
|
96
102
|
readonly kind: OwnedUiTranscriptBlockKind;
|
|
@@ -99,6 +105,8 @@ export interface OwnedUiTranscriptBlock {
|
|
|
99
105
|
readonly title: string | null;
|
|
100
106
|
readonly text: string;
|
|
101
107
|
readonly payload: unknown;
|
|
108
|
+
/** Bounded opaque references; image bytes remain in the session-scoped engine asset store. */
|
|
109
|
+
readonly imageReferences?: readonly OwnedUiTranscriptImageReference[];
|
|
102
110
|
}
|
|
103
111
|
export type OwnedUiSlotId = "theme" | "transcript-block" | "tool-card" | "editor" | "status" | "command" | "selector" | "dialog" | "overlay" | "layout";
|
|
104
112
|
export interface OwnedUiCustomization {
|
|
@@ -14,6 +14,7 @@ const MAX_QUEUE = 32;
|
|
|
14
14
|
const MAX_BADGES = 32;
|
|
15
15
|
const MAX_STATUS_DIAGNOSTICS = 32;
|
|
16
16
|
const MAX_ACTIVE_COMMANDS = 64;
|
|
17
|
+
const IMAGE_REFERENCE_SOURCES = new Set(["user", "tool-result"]);
|
|
17
18
|
const BLOCK_KINDS = new Set([
|
|
18
19
|
"user",
|
|
19
20
|
"assistant",
|
|
@@ -225,6 +226,16 @@ export function assertOwnedUiTranscriptBlock(block) {
|
|
|
225
226
|
assertOptionalText(block.title, "owned-UI transcript block title", MAX_LABEL_LENGTH);
|
|
226
227
|
assertPossiblyEmptyText(block.text, "owned-UI transcript block text", MAX_TEXT_BYTES);
|
|
227
228
|
assertJsonValue(block.payload, "owned-UI transcript block payload", MAX_PAYLOAD_BYTES);
|
|
229
|
+
if (block.imageReferences !== undefined) {
|
|
230
|
+
assertCollection(block.imageReferences, "owned-UI transcript image references", 16);
|
|
231
|
+
for (const reference of block.imageReferences) {
|
|
232
|
+
assertId(reference.assetId, "owned-UI transcript image asset id");
|
|
233
|
+
if (!/^image\/[a-z0-9.+-]+$/i.test(reference.mimeType))
|
|
234
|
+
throw new TypeError("owned-UI transcript image MIME type is invalid");
|
|
235
|
+
assertIntegerInRange(reference.byteLength, 1, 20 * 1024 * 1024, "owned-UI transcript image byte length");
|
|
236
|
+
assertEnum(reference.source, IMAGE_REFERENCE_SOURCES, "owned-UI transcript image source");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
228
239
|
}
|
|
229
240
|
export function assertOwnedUiEditorState(editor) {
|
|
230
241
|
assertPossiblyEmptyText(editor.text, "owned-UI editor text", MAX_TEXT_BYTES);
|