@timurproko/a1 0.1.8-dev.226 → 0.1.8-dev.259

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 (94) hide show
  1. package/bin/guardian.js +4 -2
  2. package/bin/supervisor.js +1 -1
  3. package/bin/ui.js +44 -36
  4. package/bin/warmup.js +18 -9
  5. package/dist/cli/dispatch.d.ts +9 -1
  6. package/dist/cli/dispatch.js +73 -14
  7. package/dist/cli/package-messages.d.ts +26 -0
  8. package/dist/cli/package-messages.js +71 -0
  9. package/dist/cli/packages.d.ts +2 -1
  10. package/dist/cli/packages.js +12 -19
  11. package/dist/composition/owned-ui.d.ts +5 -2
  12. package/dist/composition/owned-ui.js +3 -1
  13. package/dist/contracts/agent-engine/package-ports.d.ts +6 -0
  14. package/dist/features/launch/intent.d.ts +5 -3
  15. package/dist/features/launch/intent.js +7 -2
  16. package/dist/features/owned-ui/index.d.ts +1 -0
  17. package/dist/features/owned-ui/index.js +1 -0
  18. package/dist/features/owned-ui/session-fork-prompt.d.ts +8 -0
  19. package/dist/features/owned-ui/session-fork-prompt.js +34 -0
  20. package/dist/foundation/launch-guardian/main.d.ts +2 -0
  21. package/dist/foundation/launch-guardian/main.js +8 -3
  22. package/dist/foundation/lifecycle/index.d.ts +2 -0
  23. package/dist/foundation/lifecycle/index.js +2 -0
  24. package/dist/foundation/lifecycle/paths.d.ts +2 -0
  25. package/dist/foundation/lifecycle/paths.js +9 -2
  26. package/dist/foundation/lifecycle/session-selection.d.ts +9 -0
  27. package/dist/foundation/lifecycle/session-selection.js +39 -0
  28. package/dist/foundation/lifecycle/supervisor-startup.d.ts +30 -0
  29. package/dist/foundation/lifecycle/supervisor-startup.js +106 -0
  30. package/dist/foundation/process-containment/darwin-process-inspector.d.ts +5 -0
  31. package/dist/foundation/process-containment/darwin-process-inspector.js +7 -0
  32. package/dist/foundation/process-containment/index.d.ts +1 -0
  33. package/dist/foundation/process-containment/index.js +1 -0
  34. package/dist/foundation/process-containment/windows-process-inspector.d.ts +2 -1
  35. package/dist/foundation/process-containment/windows-process-inspector.js +4 -2
  36. package/dist/foundation/release/bootstrap.d.ts +10 -3
  37. package/dist/foundation/release/bootstrap.js +58 -25
  38. package/dist/foundation/release/dependency-layer.js +11 -10
  39. package/dist/foundation/release/immutable-platform.d.ts +6 -0
  40. package/dist/foundation/release/immutable-platform.js +13 -0
  41. package/dist/foundation/release/index.d.ts +1 -0
  42. package/dist/foundation/release/index.js +1 -0
  43. package/dist/foundation/release/release-store.d.ts +1 -1
  44. package/dist/foundation/release/release-store.js +4 -2
  45. package/dist/foundation/release/restart-certification.d.ts +67 -0
  46. package/dist/foundation/release/restart-certification.js +211 -0
  47. package/dist/foundation/release/update.js +4 -4
  48. package/dist/foundation/release/warmup.js +1 -1
  49. package/dist/foundation/startup/startup-runtime.d.ts +2 -2
  50. package/dist/foundation/startup/startup-runtime.js +1 -1
  51. package/dist/foundation/supervision/main.d.ts +1 -1
  52. package/dist/foundation/supervision/main.js +60 -30
  53. package/dist/foundation/supervision/server.js +14 -1
  54. package/dist/integrations/pi/components/shell-footer-status.js +28 -12
  55. package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +4 -1
  56. package/dist/integrations/pi/components/shell-selectors-dialogs.js +2 -1
  57. package/dist/integrations/pi/components/shell-shared-facade.d.ts +5 -0
  58. package/dist/integrations/pi/engine/adapter.d.ts +6 -0
  59. package/dist/integrations/pi/engine/adapter.js +354 -53
  60. package/dist/integrations/pi/engine/index.d.ts +1 -0
  61. package/dist/integrations/pi/engine/index.js +1 -0
  62. package/dist/integrations/pi/engine/package-integration.js +31 -25
  63. package/dist/integrations/pi/engine/runtime-integration.d.ts +3 -0
  64. package/dist/integrations/pi/engine/runtime-integration.js +11 -4
  65. package/dist/integrations/pi/engine/session-selection.d.ts +28 -0
  66. package/dist/integrations/pi/engine/session-selection.js +90 -0
  67. package/dist/integrations/pi/engine/workflows.d.ts +18 -1
  68. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +5 -1
  69. package/dist/integrations/pi/session-ui/session-shell-root.js +76 -24
  70. package/dist/integrations/pi/session-ui/session-shell.d.ts +1 -1
  71. package/dist/integrations/pi/session-ui/session-shell.js +46 -14
  72. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +2 -1
  73. package/dist/integrations/pi/session-ui/session-viewport-controller.js +47 -18
  74. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +12 -1
  75. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +166 -26
  76. package/dist/native/darwin-arm64/manifest.json +4 -4
  77. package/dist/native/darwin-arm64/process-guardian +0 -0
  78. package/dist/native/linux-x64/manifest.json +1 -1
  79. package/dist/native/linux-x64/process-guardian +0 -0
  80. package/dist/native/win32-x64/manifest.json +2 -2
  81. package/dist/native/win32-x64/process-guardian.exe +0 -0
  82. package/dist/ui/components/index.d.ts +1 -0
  83. package/dist/ui/components/index.js +1 -0
  84. package/dist/ui/components/text-selection.js +8 -35
  85. package/dist/ui/components/transcript-viewport.d.ts +8 -2
  86. package/dist/ui/components/transcript-viewport.js +8 -6
  87. package/dist/ui/components/visible-hyperlinks.d.ts +19 -0
  88. package/dist/ui/components/visible-hyperlinks.js +64 -0
  89. package/docs/architecture/process-guardian-provenance.md +3 -1
  90. package/docs/architecture/ui-reference-provenance.md +2 -0
  91. package/docs/ci-release-runbook.md +1 -1
  92. package/docs/features/launch-profiles.md +17 -1
  93. package/docs/manual-ghost-link-baseline.md +89 -0
  94. package/package.json +1 -1
package/bin/guardian.js CHANGED
@@ -5,18 +5,20 @@ startup.enableEnvironmentCompileCache(process.env);
5
5
  await startup.markStartupPhase(process.env, "guardian-start");
6
6
  const { fileURLToPath } = await import("node:url");
7
7
  const { resolve } = await import("node:path");
8
+ const { parseSessionSelection } = await import("../dist/foundation/lifecycle/index.js");
8
9
  const { runLaunchGuardian } = await import("../dist/foundation/launch-guardian/index.js");
9
10
 
10
11
  const releaseRoot = process.env.A1_RELEASE_ROOT ?? fileURLToPath(new URL("..", import.meta.url));
11
12
  const profileId = process.env.A1_LAUNCH_PROFILE ?? "a1";
12
13
 
13
- runLaunchGuardian({
14
+ Promise.resolve().then(() => runLaunchGuardian({
15
+ sessionSelection: parseSessionSelection(process.argv.slice(2)),
14
16
  profileId,
15
17
  releaseRoot,
16
18
  uiEntry: resolve(releaseRoot, "bin", "ui.js"),
17
19
  environment: process.env,
18
20
  cwd: process.cwd(),
19
- }).then(
21
+ })).then(
20
22
  code => { process.exitCode = code; },
21
23
  error => {
22
24
  console.error(error instanceof Error ? error.message : String(error));
package/bin/supervisor.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { runSupervisor } from "../dist/foundation/supervision/index.js";
3
3
 
4
- runSupervisor().catch(error => {
4
+ runSupervisor(process.argv.slice(2)).catch(error => {
5
5
  console.error(error instanceof Error ? error.stack ?? error.message : String(error));
6
6
  process.exitCode = 1;
7
7
  });
package/bin/ui.js CHANGED
@@ -2,48 +2,56 @@
2
2
 
3
3
  const startup = await import("../dist/foundation/startup/index.js");
4
4
  startup.enableEnvironmentCompileCache(process.env);
5
+ // Performance: begin the exact launch graph together while the trace write is pending.
6
+ // Direct owned-module entries avoid evaluating unrelated barrel exports before first paint.
7
+ const modules = Promise.all([
8
+ import("./module-identity.js"),
9
+ import("node:url"),
10
+ import("../dist/features/launch/runtime-selection.js"),
11
+ import("../dist/foundation/lifecycle/session-selection.js"),
12
+ import("../dist/features/owned-ui/project-trust-prompt.js"),
13
+ import("../dist/features/owned-ui/session-fork-prompt.js"),
14
+ import("../dist/features/owned-ui/run.js"),
15
+ import("../dist/composition/owned-ui.js"),
16
+ ]);
5
17
  await startup.markStartupPhase(process.env, "ui-entry");
6
- const { assertSinglePiTuiModuleAtLaunch } = await import("./module-identity.js");
7
- const { fileURLToPath } = await import("node:url");
18
+ const [
19
+ { assertSinglePiTuiModuleAtLaunch },
20
+ { fileURLToPath },
21
+ { runSelectedInteractiveRuntime },
22
+ { parseSessionSelection },
23
+ { createConsoleProjectTrustPrompt },
24
+ { createConsoleSessionForkPrompt },
25
+ { runOwnedUi },
26
+ { composeOwnedUi },
27
+ ] = await modules;
8
28
 
9
- // Compatibility: before the composition loads pinned Pi's terminal stack: confirm A1 and Pi
10
- // resolve it to the same copy, so extensions and the owned UI share one module
11
- // identity (see bin/module-identity.js for the full story). Nothing is repaired
12
- // here — A1's package manifest decides which copy wins, and this only reports
13
- // when that stopped being true.
29
+ // Compatibility: before the composition uses pinned Pi's terminal stack: confirm A1 and Pi
30
+ // resolve it to the same copy, so extensions and the owned UI share one module identity.
14
31
  assertSinglePiTuiModuleAtLaunch(fileURLToPath(new URL("..", import.meta.url)), message => process.stderr.write(message));
32
+ await startup.markStartupPhase(process.env, "ui-modules-loaded");
15
33
 
16
- const { runSelectedInteractiveRuntime } = await import("../dist/features/launch/index.js");
17
-
18
- const launchArgs = process.argv.slice(2);
19
- let sessionPath;
20
- if (launchArgs.length > 0) {
21
- if (launchArgs.length !== 2 || launchArgs[0] !== "--session" || launchArgs[1].trim().length === 0) {
22
- throw new Error("Usage: a1 [--session <session-file>]");
23
- }
24
- sessionPath = launchArgs[1];
25
- }
26
-
27
- runSelectedInteractiveRuntime(process.env.A1_LAUNCH_PROFILE ?? "a1", {
28
- ownedUi: async (profileId, ownedSurfaces) => {
29
- const [{ createConsoleProjectTrustPrompt, runOwnedUi }, { composeOwnedUi }] = await Promise.all([
30
- import("../dist/features/owned-ui/index.js"),
31
- import("../dist/composition/index.js"),
32
- ]);
33
- await startup.markStartupPhase(process.env, "ui-modules-loaded");
34
- const { application, settings } = await composeOwnedUi({
35
- cwd: process.cwd(),
36
- profileId,
37
- ownedSurfaces,
38
- projectTrustPrompt: createConsoleProjectTrustPrompt(),
39
- ...(sessionPath === undefined ? {} : { sessionPath }),
40
- });
41
- return await runOwnedUi({ application, ...(settings === null ? {} : { settings }) });
42
- },
34
+ const sessionSelection = parseSessionSelection(process.argv.slice(2));
35
+ const profile = process.env.A1_LAUNCH_PROFILE ?? "a1";
36
+ Promise.resolve().then(() => {
37
+ if (sessionSelection && profile !== "a1") throw new Error("session selection requires the normal A1 profile");
38
+ return runSelectedInteractiveRuntime(profile, {
39
+ ownedUi: async (profileId, ownedSurfaces) => {
40
+ const { application, settings } = await composeOwnedUi({
41
+ cwd: process.cwd(),
42
+ profileId,
43
+ ownedSurfaces,
44
+ projectTrustPrompt: createConsoleProjectTrustPrompt(),
45
+ sessionForkPrompt: createConsoleSessionForkPrompt(),
46
+ ...(sessionSelection === undefined ? {} : { sessionSelection }),
47
+ });
48
+ return await runOwnedUi({ application, ...(settings === null ? {} : { settings }) });
49
+ },
50
+ });
43
51
  }).then(
44
52
  code => { process.exitCode = code; },
45
53
  error => {
46
- console.error(error instanceof Error ? error.stack ?? error.message : String(error));
47
- process.exitCode = 1;
54
+ console.error(error instanceof Error ? error.message : String(error));
55
+ process.exitCode = error?.name === "PiSessionSelectionError" ? error.exitCode : 1;
48
56
  },
49
57
  );
package/bin/warmup.js CHANGED
@@ -4,15 +4,24 @@ const startup = await import("../dist/foundation/startup/index.js");
4
4
  startup.assertImmutableWarmupEnvironment(process.env);
5
5
  startup.enableEnvironmentCompileCache(process.env);
6
6
 
7
- // Security: this entry imports but never composes, so it creates no terminal, profile/session
8
- // path, trust callback, executable resource loader, extension runner, or network client.
9
- const [composition, engine, components, runtime] = await Promise.all([
10
- import("../dist/composition/index.js"),
11
- import("../dist/integrations/pi/engine/index.js"),
12
- import("../dist/integrations/pi/components/index.js"),
13
- import("../dist/integrations/pi/tui-runtime/index.js"),
7
+ // Security: this entry imports the exact interactive graph but never composes it, so it
8
+ // creates no terminal, profile/session path, trust callback, executable resource loader,
9
+ // extension runner, or network client.
10
+ const [identity, launch, selection, trustPrompt, forkPrompt, run, composition] = await Promise.all([
11
+ import("./module-identity.js"),
12
+ import("../dist/features/launch/runtime-selection.js"),
13
+ import("../dist/foundation/lifecycle/session-selection.js"),
14
+ import("../dist/features/owned-ui/project-trust-prompt.js"),
15
+ import("../dist/features/owned-ui/session-fork-prompt.js"),
16
+ import("../dist/features/owned-ui/run.js"),
17
+ import("../dist/composition/owned-ui.js"),
14
18
  ]);
15
- if (typeof composition.composeOwnedUi !== "function" || typeof engine.createPiEngineAdapter !== "function"
16
- || typeof components.applyConfiguredPiTheme !== "function" || typeof runtime.createPiTerminalBridge !== "function") {
19
+ if (typeof identity.assertSinglePiTuiModuleAtLaunch !== "function"
20
+ || typeof launch.runSelectedInteractiveRuntime !== "function"
21
+ || typeof selection.parseSessionSelection !== "function"
22
+ || typeof trustPrompt.createConsoleProjectTrustPrompt !== "function"
23
+ || typeof forkPrompt.createConsoleSessionForkPrompt !== "function"
24
+ || typeof run.runOwnedUi !== "function"
25
+ || typeof composition.composeOwnedUi !== "function") {
17
26
  throw new Error("immutable startup graph is incomplete");
18
27
  }
@@ -1,6 +1,7 @@
1
- import { type InteractiveLaunchIntent, type LaunchProfileId } from "../features/launch/index.js";
1
+ import { type InteractiveLaunchIntent, type LaunchProfileId, type SessionSelection } from "../features/launch/index.js";
2
2
  import type { CliCapabilities } from "./capabilities.js";
3
3
  import type { PackageCommandRequest } from "./packages.js";
4
+ import { type PackageCliVerb, type PackageSyntaxDiagnostic } from "./package-messages.js";
4
5
  export type UpdateChannel = "stable" | "next";
5
6
  export interface CliHandlers {
6
7
  readonly launch: (intent: InteractiveLaunchIntent) => Promise<number>;
@@ -19,9 +20,16 @@ export type CliCommand = {
19
20
  readonly kind: "noop";
20
21
  } | {
21
22
  readonly kind: "help";
23
+ } | {
24
+ readonly kind: "package-help";
25
+ readonly verb: PackageCliVerb;
26
+ } | {
27
+ readonly kind: "package-error";
28
+ readonly diagnostic: PackageSyntaxDiagnostic;
22
29
  } | {
23
30
  readonly kind: "launch";
24
31
  readonly profileId: LaunchProfileId;
32
+ readonly sessionSelection?: SessionSelection;
25
33
  } | {
26
34
  readonly kind: "version";
27
35
  } | {
@@ -1,8 +1,11 @@
1
- import { interactiveLaunchIntent } from "../features/launch/index.js";
1
+ import { interactiveLaunchIntent, parseSessionSelection } from "../features/launch/index.js";
2
+ import { packageCommandHelp, renderPackageSyntax } from "./package-messages.js";
2
3
  import { PRODUCT_TEXT } from "../product-identity.js";
3
4
  export function cliUsage(capabilities) {
4
5
  return PRODUCT_TEXT.usage([
5
6
  "",
7
+ "--session <path|id>",
8
+ "--session-dir <dir> --session <path|id>",
6
9
  ...(capabilities.developmentComparison ? ["pi"] : []),
7
10
  "--help",
8
11
  "-h",
@@ -25,6 +28,8 @@ export function cliHelp(capabilities) {
25
28
  return [
26
29
  "Common:",
27
30
  ` ${command}`,
31
+ ` ${command} --session <path|id>`,
32
+ ` ${command} --session-dir <dir> --session <path|id>`,
28
33
  ...(capabilities.developmentComparison ? [` ${command} pi`] : []),
29
34
  ` ${command} --help`,
30
35
  ` ${command} -h`,
@@ -56,12 +61,20 @@ export async function dispatchCli(arguments_, handlers, output, capabilities) {
56
61
  output.stdout(cliHelp(capabilities));
57
62
  return 0;
58
63
  }
64
+ if (command.kind === "package-help") {
65
+ output.stdout(packageCommandHelp(command.verb));
66
+ return 0;
67
+ }
68
+ if (command.kind === "package-error") {
69
+ output.stderr(renderPackageSyntax(command.diagnostic));
70
+ return 2;
71
+ }
59
72
  if (command.kind === "error") {
60
73
  output.stderr(`${command.message}\n`);
61
74
  return 2;
62
75
  }
63
76
  if (command.kind === "launch")
64
- return await handlers.launch(interactiveLaunchIntent(command.profileId));
77
+ return await handlers.launch(interactiveLaunchIntent(command.profileId, command.sessionSelection));
65
78
  if (command.kind === "version")
66
79
  return await handlers.version();
67
80
  if (command.kind === "packages")
@@ -72,6 +85,15 @@ export function parseCliCommand(arguments_, capabilities) {
72
85
  if (arguments_.length === 0)
73
86
  return { kind: "launch", profileId: "a1" };
74
87
  const [command, ...rest] = arguments_;
88
+ if (command === "--session" || command === "--session-dir") {
89
+ try {
90
+ const sessionSelection = parseSessionSelection(arguments_);
91
+ return { kind: "launch", profileId: "a1", ...(sessionSelection === undefined ? {} : { sessionSelection }) };
92
+ }
93
+ catch (error) {
94
+ return { kind: "error", message: PRODUCT_TEXT.diagnostic(`could not parse session launch: ${error instanceof Error ? error.message : String(error)}`) };
95
+ }
96
+ }
75
97
  if (command === "--help" || command === "-h")
76
98
  return withoutArguments(rest, { kind: "help" });
77
99
  if (command === "--version" || command === "-v")
@@ -132,11 +154,21 @@ function updateGrammarError(detail) {
132
154
  }
133
155
  function parsePiPackageCommand(arguments_) {
134
156
  const [verb, ...rest] = arguments_;
157
+ if ((verb === "install" || verb === "remove" || verb === "uninstall" || verb === "list" || verb === "update")
158
+ && rest.some(value => value === "--help" || value === "-h")) {
159
+ return { kind: "package-help", verb: verb === "uninstall" ? "remove" : verb };
160
+ }
135
161
  if (verb === "install" || verb === "remove" || verb === "uninstall") {
136
162
  return parseSourceCommand(verb === "install" ? "install" : "remove", rest);
137
163
  }
138
- if (verb === "list")
139
- return withoutArguments(rest, { kind: "packages", request: { verb: "list", source: null } });
164
+ if (verb === "list") {
165
+ const flag = rest.find(argument => argument.startsWith("-"));
166
+ if (flag !== undefined)
167
+ return packageOptionError(flag, "list");
168
+ if (rest[0] !== undefined)
169
+ return packageSyntax({ verb: "list", kind: "unexpected-argument", value: rest[0] });
170
+ return { kind: "packages", request: { verb: "list", source: null } };
171
+ }
140
172
  if (verb === "update")
141
173
  return parsePiPackageUpdate(rest);
142
174
  return { kind: "noop" };
@@ -144,8 +176,25 @@ function parsePiPackageCommand(arguments_) {
144
176
  function parsePiPackageUpdate(rest) {
145
177
  if (rest.length === 0)
146
178
  return pinnedPiUpdateError();
147
- if (rest.length > 1)
148
- return { kind: "error", message: PRODUCT_TEXT.diagnostic("pi update accepts one target.") };
179
+ if (rest.length > 1) {
180
+ const unknown = rest.find(value => value.startsWith("-") && !["--extensions", "--models", "--self", "--all"].includes(value));
181
+ if (unknown !== undefined)
182
+ return packageOptionError(unknown, "update");
183
+ if (rest.includes("--self") || rest.includes("--all") || rest.includes("self") || rest.includes("pi"))
184
+ return pinnedPiUpdateError();
185
+ if (rest.every(value => value === rest[0]) && rest[0]?.startsWith("-")) {
186
+ return { kind: "error", message: PRODUCT_TEXT.diagnostic("pi update accepts one target.") };
187
+ }
188
+ const sources = rest.filter(value => !value.startsWith("-"));
189
+ if (sources[1] !== undefined)
190
+ return packageSyntax({ verb: "update", kind: "unexpected-argument", value: sources[1] });
191
+ const message = rest.includes("--models")
192
+ ? rest.includes("--extensions")
193
+ ? "--models cannot be combined with --self, --extensions, --all, or --extension"
194
+ : "--models cannot be combined with a positional source"
195
+ : "positional update targets cannot be combined with --self, --extensions, or --all";
196
+ return packageSyntax({ verb: "update", kind: "conflict", message });
197
+ }
149
198
  const [target] = rest;
150
199
  if (target === "--extensions")
151
200
  return { kind: "packages", request: { verb: "update", source: null } };
@@ -154,7 +203,7 @@ function parsePiPackageUpdate(rest) {
154
203
  if (target === "--self" || target === "--all" || target === "self" || target === "pi")
155
204
  return pinnedPiUpdateError();
156
205
  if (target === undefined || target.startsWith("-"))
157
- return unknownOption(target ?? "", "pi update");
206
+ return packageOptionError(target ?? "", "update");
158
207
  return { kind: "packages", request: { verb: "update", source: target } };
159
208
  }
160
209
  function pinnedPiUpdateError() {
@@ -172,18 +221,28 @@ function pinnedPiUpdateError() {
172
221
  function parseSourceCommand(verb, rest) {
173
222
  const flag = rest.find(argument => argument.startsWith("-"));
174
223
  if (flag !== undefined)
175
- return unknownOption(flag, `pi ${verb}`);
176
- if (rest.length === 0)
177
- return { kind: "error", message: PRODUCT_TEXT.diagnostic(`${verb} requires a package source.`) };
178
- if (rest.length > 1)
179
- return { kind: "error", message: PRODUCT_TEXT.diagnostic(`${verb} accepts one package source.`) };
180
- const [source] = rest;
224
+ return packageOptionError(flag, verb);
225
+ const [source, extra] = rest;
226
+ if (extra !== undefined)
227
+ return packageSyntax({ verb, kind: "unexpected-argument", value: extra });
181
228
  if (source === undefined)
182
- return { kind: "error", message: PRODUCT_TEXT.diagnostic(`${verb} requires a package source.`) };
229
+ return packageSyntax({ verb, kind: "missing-source" });
183
230
  if (PROFILE_WORDS.has(source))
184
231
  return profileRejection(verb);
185
232
  return { kind: "packages", request: { verb, source } };
186
233
  }
234
+ function packageSyntax(diagnostic) {
235
+ return { kind: "package-error", diagnostic };
236
+ }
237
+ function packageOptionError(option, verb) {
238
+ if (option.startsWith("--profile") || option === "-l" || option === "--local")
239
+ return profileRejection(verb);
240
+ // Compatibility: these recognized Pi flags remain outside A1's supported subset.
241
+ if (["--extension", "--approve", "--no-approve", "-a", "-na", "--force"].includes(option)) {
242
+ return unknownOption(option, `pi ${verb}`);
243
+ }
244
+ return packageSyntax({ verb, kind: "unknown-option", value: option });
245
+ }
187
246
  function withoutArguments(rest, command) {
188
247
  if (rest.length === 0)
189
248
  return command;
@@ -0,0 +1,26 @@
1
+ import chalk from "chalk";
2
+ /** Supported subset of pinned Pi's package-manager CLI presentation. */
3
+ export type PackageCliVerb = "install" | "remove" | "list" | "update";
4
+ export type PackageSyntaxDiagnostic = {
5
+ readonly verb: PackageCliVerb;
6
+ readonly kind: "missing-source";
7
+ } | {
8
+ readonly verb: PackageCliVerb;
9
+ readonly kind: "unknown-option";
10
+ readonly value: string;
11
+ } | {
12
+ readonly verb: PackageCliVerb;
13
+ readonly kind: "unexpected-argument";
14
+ readonly value: string;
15
+ } | {
16
+ readonly verb: "update";
17
+ readonly kind: "conflict";
18
+ readonly message: string;
19
+ };
20
+ type MessageStyle = Pick<typeof chalk, "red" | "dim" | "bold">;
21
+ export declare function packageCommandUsage(verb: PackageCliVerb): string;
22
+ /** Diagnostic facts stay unstyled until the terminal boundary decides color support. */
23
+ export declare function renderPackageSyntax(diagnostic: PackageSyntaxDiagnostic, style?: MessageStyle): string;
24
+ /** Pi's help layout, with unsupported options and examples deliberately absent. */
25
+ export declare function packageCommandHelp(verb: PackageCliVerb, style?: MessageStyle): string;
26
+ export {};
@@ -0,0 +1,71 @@
1
+ // Provenance: wording/layout adapted from Pi 0.84.2 packages/coding-agent/src/package-manager-cli.ts
2
+ // at 914cf1472e715297caa30db4b9535d534a9eb718 (MIT); grammar is restricted to A1's supported subset.
3
+ import chalk from "chalk";
4
+ import { PRODUCT_TEXT } from "../product-identity.js";
5
+ export function packageCommandUsage(verb) {
6
+ const command = `${PRODUCT_TEXT.commandName} pi ${verb}`;
7
+ if (verb === "install" || verb === "remove")
8
+ return `${command} <source>`;
9
+ if (verb === "update")
10
+ return `${command} [source|--extensions|--models]`;
11
+ return command;
12
+ }
13
+ /** Diagnostic facts stay unstyled until the terminal boundary decides color support. */
14
+ export function renderPackageSyntax(diagnostic, style = chalk) {
15
+ const usage = packageCommandUsage(diagnostic.verb);
16
+ let message;
17
+ if (diagnostic.kind === "unknown-option") {
18
+ return `${style.red(`Unknown option ${diagnostic.value} for "${diagnostic.verb}".`)}\n`
19
+ + `${style.dim(`Use "${PRODUCT_TEXT.commandName} --help" or "${usage}".`)}\n`;
20
+ }
21
+ if (diagnostic.kind === "missing-source")
22
+ message = `Missing ${diagnostic.verb} source.`;
23
+ else if (diagnostic.kind === "unexpected-argument")
24
+ message = `Unexpected argument ${diagnostic.value}.`;
25
+ else
26
+ message = diagnostic.message;
27
+ return `${style.red(message)}\n${style.dim(`Usage: ${usage}`)}\n`;
28
+ }
29
+ /** Pi's help layout, with unsupported options and examples deliberately absent. */
30
+ export function packageCommandHelp(verb, style = chalk) {
31
+ const command = `${PRODUCT_TEXT.commandName} pi`;
32
+ const header = `${style.bold("Usage:")}\n ${packageCommandUsage(verb)}\n\n`;
33
+ switch (verb) {
34
+ case "install":
35
+ return header + `Install a package and add it to settings.
36
+
37
+ Examples:
38
+ ${command} install npm:@foo/bar
39
+ ${command} install git:github.com/user/repo
40
+ ${command} install git:git@github.com:user/repo
41
+ ${command} install https://github.com/user/repo
42
+ ${command} install ssh://git@github.com/user/repo
43
+ ${command} install ./local/path
44
+
45
+ `;
46
+ case "remove":
47
+ return header + `Remove a package and its source from settings.
48
+ Alias: ${command} uninstall <source>
49
+
50
+ Examples:
51
+ ${command} remove npm:@foo/bar
52
+ ${command} uninstall npm:@foo/bar
53
+
54
+ `;
55
+ case "list":
56
+ return header + "List installed packages from user settings.\n\n";
57
+ case "update":
58
+ return header + `Update installed packages or model catalogs.
59
+
60
+ Options:
61
+ --extensions Update installed packages only
62
+ --models Refresh model catalogs only
63
+
64
+ Short forms:
65
+ ${command} update --extensions Update installed packages only
66
+ ${command} update --models Refresh model catalogs only
67
+ ${command} update <source> Update one package
68
+
69
+ `;
70
+ }
71
+ }
@@ -16,6 +16,7 @@ export interface PackageCommandStyle {
16
16
  readonly bold: (message: string) => string;
17
17
  readonly green: (message: string) => string;
18
18
  readonly red: (message: string) => string;
19
+ readonly yellow: (message: string) => string;
19
20
  }
20
21
  export interface PackageCommandEnvironment {
21
22
  readonly createPort: (input: AgentPackagesPortInput) => AgentPackagesPort;
@@ -28,4 +29,4 @@ export interface PackageCommandEnvironment {
28
29
  readonly style?: PackageCommandStyle;
29
30
  }
30
31
  export declare function runPackageCommand(request: PackageCommandRequest, environment: PackageCommandEnvironment): Promise<number>;
31
- export declare function renderPackageOutcome(outcome: AgentPackageOutcome, profileRoot: string, style?: PackageCommandStyle): string;
32
+ export declare function renderPackageOutcome(outcome: AgentPackageOutcome, style?: PackageCommandStyle): string;
@@ -21,10 +21,15 @@ export async function runPackageCommand(request, environment) {
21
21
  const port = environment.createPort({
22
22
  profileRoot,
23
23
  cwd,
24
- onProgress: progress => stdout(`${style.dim(progress.message)}\n`),
24
+ onProgress: progress => stdout(style.dim(`${progress.message}\n`)),
25
+ onDiagnostic: diagnostic => {
26
+ stderr(`${style.yellow(diagnostic.message)}\n`);
27
+ if (diagnostic.detail)
28
+ stderr(`${style.dim(diagnostic.detail)}\n`);
29
+ },
25
30
  });
26
31
  const outcome = await runVerb(port, request);
27
- const rendered = renderPackageOutcome(outcome, profileRoot, style);
32
+ const rendered = renderPackageOutcome(outcome, style);
28
33
  (outcome.status === "completed" ? stdout : stderr)(rendered);
29
34
  return outcome.status === "completed" ? 0 : 1;
30
35
  }
@@ -39,22 +44,17 @@ async function runVerb(port, request) {
39
44
  throw new Error(PRODUCT_TEXT.diagnostic(`requires a source for ${request.verb}`));
40
45
  return request.verb === "install" ? await port.install(request.source) : await port.remove(request.source);
41
46
  }
42
- export function renderPackageOutcome(outcome, profileRoot, style = chalk) {
43
- // Compatibility: model refresh remains an A1 top-level command. The `a1 pi` compatibility
44
- // transcript applies to package operations only.
45
- if (outcome.operation === "refresh-models") {
46
- if (outcome.status === "failed") {
47
- return `${PRODUCT_TEXT.diagnostic(`could not ${describeOperation(outcome.operation)}: ${outcome.detail ?? "unknown failure"}`)}\n`;
48
- }
49
- return `${PRODUCT_TEXT.displayName} refreshed the model catalogs in ${profileRoot}.\n`;
50
- }
47
+ export function renderPackageOutcome(outcome, style = chalk) {
51
48
  if (outcome.status === "failed") {
52
- return `${style.red(`Error: ${outcome.detail ?? "Unknown package command error"}`)}\n`;
49
+ const fallback = outcome.operation === "refresh-models" ? "Unknown model catalog refresh error" : "Unknown package command error";
50
+ return `${style.red(`Error: ${outcome.detail ?? fallback}`)}\n`;
53
51
  }
54
52
  if (outcome.status === "not-found") {
55
53
  return `${style.red(`No matching package found for ${outcome.source ?? "that source"}`)}\n`;
56
54
  }
57
55
  switch (outcome.operation) {
56
+ case "refresh-models":
57
+ return `${style.green("Model catalogs refreshed")}\n`;
58
58
  case "install":
59
59
  return `${style.green(`Installed ${outcome.source}`)}\n`;
60
60
  case "remove":
@@ -76,13 +76,6 @@ function renderPackageList(outcome, style) {
76
76
  }
77
77
  return `${lines.join("\n")}\n`;
78
78
  }
79
- function describeOperation(operation) {
80
- if (operation === "refresh-models")
81
- return "refresh the model catalogs";
82
- if (operation === "list")
83
- return "list installed packages";
84
- return `${operation} the package`;
85
- }
86
79
  function message(error) {
87
80
  return (error instanceof Error ? error.message : String(error)).replace(/\s+/g, " ").trim();
88
81
  }
@@ -1,12 +1,15 @@
1
- import { type PiEngineAdapter, type PiProjectTrustPreflightPrompt } from "../integrations/pi/engine/index.js";
1
+ import { type SessionSelection } from "../foundation/lifecycle/index.js";
2
+ import { type PiEngineAdapter, type PiProjectTrustPreflightPrompt, type PiSessionForkPrompt } from "../integrations/pi/engine/index.js";
2
3
  import { OwnedUiSettingsSession } from "../ui/settings/index.js";
3
4
  import type { OwnedUiApplicationPort, PresentationTerminalPort } from "../contracts/presentation/index.js";
4
5
  export interface OwnedUiCompositionOptions {
5
6
  readonly cwd?: string;
6
7
  readonly terminal?: PresentationTerminalPort;
7
8
  readonly createPiAdapter?: () => Promise<PiEngineAdapter>;
8
- /** Exact persisted session selected by the narrow `--session` launch form. */
9
+ /** Explicit file selection retained for SDK callers. Public CLI uses sessionSelection. */
9
10
  readonly sessionPath?: string;
11
+ readonly sessionSelection?: SessionSelection;
12
+ readonly sessionForkPrompt?: PiSessionForkPrompt;
10
13
  /**
11
14
  * A1 profile whose settings this session reads and writes. Omitted keeps the
12
15
  * session settings-free, which is what the pinned comparison paths use.
@@ -17,6 +17,8 @@ export async function composeOwnedUi(options = {}) {
17
17
  availableThemes: () => getAvailablePiThemes().map(theme => theme.name),
18
18
  settingsProductMode: options.ownedSurfaces === "off" ? "comparison" : "bare",
19
19
  ...(options.sessionPath === undefined ? {} : { sessionPath: options.sessionPath }),
20
+ ...(options.sessionSelection === undefined ? {} : { sessionSelection: options.sessionSelection }),
21
+ ...(options.sessionForkPrompt === undefined ? {} : { sessionForkPrompt: options.sessionForkPrompt }),
20
22
  ...(options.projectTrustPrompt === undefined ? {} : { projectTrustPrompt: options.projectTrustPrompt }),
21
23
  });
22
24
  const ownedSurfaces = options.ownedSurfaces !== "off";
@@ -37,7 +39,7 @@ export async function composeOwnedUi(options = {}) {
37
39
  };
38
40
  const shell = new OwnedUiSessionShell({
39
41
  backend: adapter,
40
- cwd,
42
+ cwd: adapter.cwd,
41
43
  ...(options.terminal === undefined ? {} : { terminal: createPiTerminalBridge(options.terminal) }),
42
44
  ...(routeHost === null ? {} : { routeHost }),
43
45
  ...(ownedSurfaces ? { sessionLayout: "custom-viewport" } : {}),
@@ -45,10 +45,16 @@ export interface AgentPackagesPort {
45
45
  update(source?: string): Promise<AgentPackageOutcome>;
46
46
  refreshModels(): Promise<AgentPackageOutcome>;
47
47
  }
48
+ /** Recoverable operation diagnostics retain the engine's visible detail and order. */
49
+ export interface AgentPackageDiagnostic {
50
+ readonly message: string;
51
+ readonly detail?: string;
52
+ }
48
53
  export interface AgentPackagesPortInput {
49
54
  readonly profileRoot: string;
50
55
  readonly cwd: string;
51
56
  readonly onProgress?: (progress: AgentPackageProgress) => void;
57
+ readonly onDiagnostic?: (diagnostic: AgentPackageDiagnostic) => void;
52
58
  }
53
59
  export declare function assertAgentPackagesPort(port: AgentPackagesPort): void;
54
60
  export declare function agentPackageOutcome(operation: AgentPackageOperation, status: AgentPackageStatus, detail?: string | null, source?: string | null, packages?: readonly AgentPackageDescriptor[]): AgentPackageOutcome;
@@ -1,8 +1,10 @@
1
- import type { LaunchProfileId } from "../../foundation/lifecycle/index.js";
2
- export type { LaunchProfileId } from "../../foundation/lifecycle/index.js";
1
+ import { type SessionSelection, type LaunchProfileId } from "../../foundation/lifecycle/index.js";
2
+ export { parseSessionSelection } from "../../foundation/lifecycle/index.js";
3
+ export type { LaunchProfileId, SessionSelection } from "../../foundation/lifecycle/index.js";
3
4
  export interface InteractiveLaunchIntent {
4
5
  readonly kind: "interactive";
5
6
  readonly profileId: LaunchProfileId;
7
+ readonly sessionSelection?: SessionSelection;
6
8
  }
7
9
  export type LaunchIntent = InteractiveLaunchIntent;
8
- export declare function interactiveLaunchIntent(profileId: LaunchProfileId): InteractiveLaunchIntent;
10
+ export declare function interactiveLaunchIntent(profileId: LaunchProfileId, sessionSelection?: SessionSelection): InteractiveLaunchIntent;
@@ -1,3 +1,8 @@
1
- export function interactiveLaunchIntent(profileId) {
2
- return Object.freeze({ kind: "interactive", profileId });
1
+ import { sessionSelectionArguments } from "../../foundation/lifecycle/index.js";
2
+ export { parseSessionSelection } from "../../foundation/lifecycle/index.js";
3
+ export function interactiveLaunchIntent(profileId, sessionSelection) {
4
+ if (sessionSelection !== undefined && profileId !== "a1")
5
+ throw new Error("session selection requires the normal A1 profile");
6
+ sessionSelectionArguments(sessionSelection);
7
+ return Object.freeze({ kind: "interactive", profileId, ...(sessionSelection === undefined ? {} : { sessionSelection }) });
3
8
  }
@@ -2,4 +2,5 @@ export * from "./customization.js";
2
2
  export * from "./diagnostics.js";
3
3
  export * from "./run.js";
4
4
  export * from "./project-trust-prompt.js";
5
+ export * from "./session-fork-prompt.js";
5
6
  export * from "./settings-app.js";
@@ -2,4 +2,5 @@ export * from "./customization.js";
2
2
  export * from "./diagnostics.js";
3
3
  export * from "./run.js";
4
4
  export * from "./project-trust-prompt.js";
5
+ export * from "./session-fork-prompt.js";
5
6
  export * from "./settings-app.js";
@@ -0,0 +1,8 @@
1
+ import type { Readable, Writable } from "node:stream";
2
+ /** Pre-resource, line-mode confirmation matching Pi's cross-project CLI fork decision. */
3
+ export declare function createConsoleSessionForkPrompt(options?: {
4
+ readonly input?: Readable;
5
+ readonly output?: Writable;
6
+ }): (request: {
7
+ readonly sourceCwd: string;
8
+ }) => Promise<boolean>;