@timurproko/a1 0.1.8-dev.407 → 0.1.8-dev.443

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 (106) hide show
  1. package/bin/cli.js +14 -9
  2. package/bin/guardian.js +3 -3
  3. package/bin/module-identity.js +21 -3
  4. package/bin/pinned-pi-public.d.ts +3 -0
  5. package/bin/pinned-pi-public.js +74 -0
  6. package/bin/ui.js +17 -18
  7. package/bin/warmup.js +8 -10
  8. package/dist/composition/owned-ui.d.ts +5 -3
  9. package/dist/composition/owned-ui.js +17 -10
  10. package/dist/composition/settings-route-host.d.ts +4 -5
  11. package/dist/composition/settings-route-host.js +90 -55
  12. package/dist/features/launch/intent.d.ts +5 -3
  13. package/dist/features/launch/intent.js +2 -2
  14. package/dist/features/launch/profile-paths.js +1 -1
  15. package/dist/features/launch/runtime-selection.d.ts +1 -1
  16. package/dist/features/launch/runtime-selection.js +1 -1
  17. package/dist/features/owned-ui/run.d.ts +6 -0
  18. package/dist/features/owned-ui/run.js +34 -2
  19. package/dist/features/owned-ui/settings-app.d.ts +1 -3
  20. package/dist/features/owned-ui/settings-app.js +2 -2
  21. package/dist/features/owned-ui/settings-route.d.ts +2 -0
  22. package/dist/features/owned-ui/settings-route.js +2 -0
  23. package/dist/foundation/release/update.js +5 -3
  24. package/dist/foundation/startup/index.d.ts +1 -0
  25. package/dist/foundation/startup/index.js +1 -0
  26. package/dist/foundation/startup/startup-budget.d.ts +46 -0
  27. package/dist/foundation/startup/startup-budget.js +42 -0
  28. package/dist/foundation/startup/startup-descriptor.js +34 -0
  29. package/dist/foundation/startup/startup-runtime.d.ts +0 -9
  30. package/dist/foundation/startup/startup-runtime.js +0 -14
  31. package/dist/foundation/terminal-cleanup/fatal-exit.js +4 -2
  32. package/dist/integrations/pi/components/components.js +1 -1
  33. package/dist/integrations/pi/components/owned-editor-ux.d.ts +1 -1
  34. package/dist/integrations/pi/components/owned-editor-ux.js +6 -2
  35. package/dist/integrations/pi/components/shell-editor-autocomplete.js +2 -2
  36. package/dist/integrations/pi/components/shell-extension-ui.d.ts +1 -1
  37. package/dist/integrations/pi/components/shell-extension-ui.js +1 -1
  38. package/dist/integrations/pi/components/shell-footer-status.js +1 -1
  39. package/dist/integrations/pi/components/shell-presenters-info.js +1 -1
  40. package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +2 -2
  41. package/dist/integrations/pi/components/shell-presenters-transcript.js +2 -2
  42. package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +1 -1
  43. package/dist/integrations/pi/components/shell-selectors-dialogs.js +1 -1
  44. package/dist/integrations/pi/components/shell-shared-facade.d.ts +1 -1
  45. package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +2 -0
  46. package/dist/integrations/pi/components/submitted-prompt-adapter.js +8 -2
  47. package/dist/integrations/pi/components/tool-image-presentation.d.ts +1 -1
  48. package/dist/integrations/pi/components/tool-image-presentation.js +1 -1
  49. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +1 -1
  50. package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
  51. package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
  52. package/dist/integrations/pi/components/upstream/components/first-time-setup.js +1 -1
  53. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +1 -1
  54. package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
  55. package/dist/integrations/pi/components/upstream/components/status-indicator.js +1 -1
  56. package/dist/integrations/pi/components/upstream/components/tool-execution.js +1 -1
  57. package/dist/integrations/pi/components/upstream/components/tree-selector.js +1 -1
  58. package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
  59. package/dist/integrations/pi/components/upstream/theme/theme.js +1 -1
  60. package/dist/integrations/pi/engine/adapter.d.ts +1 -1
  61. package/dist/integrations/pi/engine/adapter.js +1 -1
  62. package/dist/integrations/pi/engine/model-auth-integration.d.ts +1 -1
  63. package/dist/integrations/pi/engine/package-integration.js +1 -1
  64. package/dist/integrations/pi/engine/project-trust-preflight.js +1 -1
  65. package/dist/integrations/pi/engine/resource-extension-integration.d.ts +1 -1
  66. package/dist/integrations/pi/engine/runtime-integration.d.ts +1 -1
  67. package/dist/integrations/pi/engine/runtime-integration.js +2 -2
  68. package/dist/integrations/pi/engine/session-integration.d.ts +1 -1
  69. package/dist/integrations/pi/engine/session-selection.d.ts +1 -1
  70. package/dist/integrations/pi/engine/session-selection.js +1 -1
  71. package/dist/integrations/pi/engine/settings-integration.d.ts +1 -1
  72. package/dist/integrations/pi/engine/windows-filesystem-hygiene.d.ts +1 -1
  73. package/dist/integrations/pi/engine/windows-filesystem-hygiene.js +1 -1
  74. package/dist/integrations/pi/session-ui/clipboard-diagnostics.d.ts +1 -1
  75. package/dist/integrations/pi/session-ui/clipboard-diagnostics.js +1 -1
  76. package/dist/integrations/pi/session-ui/paste-executor.d.ts +1 -1
  77. package/dist/integrations/pi/session-ui/paste-executor.js +1 -1
  78. package/dist/integrations/pi/session-ui/paste-protocol.d.ts +1 -1
  79. package/dist/integrations/pi/session-ui/prompt-chips.d.ts +1 -1
  80. package/dist/integrations/pi/session-ui/prompt-chips.js +1 -1
  81. package/dist/integrations/pi/session-ui/response-copy-coordinator.d.ts +1 -1
  82. package/dist/integrations/pi/session-ui/response-copy-coordinator.js +1 -1
  83. package/dist/integrations/pi/session-ui/response-copy-transport.d.ts +1 -1
  84. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +8 -6
  85. package/dist/integrations/pi/session-ui/session-shell-root.js +31 -10
  86. package/dist/integrations/pi/session-ui/session-shell.d.ts +6 -3
  87. package/dist/integrations/pi/session-ui/session-shell.js +54 -11
  88. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +8 -3
  89. package/dist/integrations/pi/session-ui/session-viewport-controller.js +33 -5
  90. package/dist/integrations/pi/session-ui/system-clipboard.d.ts +1 -1
  91. package/dist/integrations/pi/session-ui/system-clipboard.js +7 -4
  92. package/dist/integrations/pi/startup-public.d.ts +2 -0
  93. package/dist/integrations/pi/startup-public.js +2128 -0
  94. package/dist/integrations/pi/startup-public.manifest.json +11696 -0
  95. package/dist/integrations/pi/tui-runtime/adapter.js +1 -1
  96. package/dist/native/darwin-arm64/manifest.json +1 -1
  97. package/dist/native/linux-x64/manifest.json +1 -1
  98. package/dist/native/win32-x64/manifest.json +2 -2
  99. package/dist/native/win32-x64/process-guardian.exe +0 -0
  100. package/dist/runtime-payload-inventory.json +4 -1
  101. package/docs/architecture/response-copy-delivery.md +3 -1
  102. package/docs/ci-release-runbook.md +20 -7
  103. package/docs/local-worktree-cleanup.md +48 -13
  104. package/docs/openspec-archive-automation.md +191 -73
  105. package/docs/validation.md +79 -0
  106. package/package.json +4 -2
package/bin/cli.js CHANGED
@@ -2,14 +2,17 @@
2
2
 
3
3
  const commandStartedAtMs = performance.timeOrigin + performance.now();
4
4
  const packageRoot = new URL("..", import.meta.url);
5
- const startup = await import("../dist/foundation/startup/index.js");
5
+ const startup = await import("../dist/foundation/startup/startup-runtime.js");
6
6
  const startupProfile = process.argv[2] === "pi" ? "pi" : "a1";
7
7
  startup.initializeStartupTrace(process.env, startupProfile, commandStartedAtMs);
8
8
  startup.enableEnvironmentCompileCache(process.env);
9
9
  await startup.markStartupPhase(process.env, "command-invoked");
10
10
  const { fileURLToPath } = await import("node:url");
11
11
  const { readFile } = await import("node:fs/promises");
12
- const { cliCapabilities, dispatchCli } = await import("../dist/cli/index.js");
12
+ const [{ cliCapabilities }, { dispatchCli }] = await Promise.all([
13
+ import("../dist/cli/capabilities.js"),
14
+ import("../dist/cli/dispatch.js"),
15
+ ]);
13
16
 
14
17
  // Compatibility: which commands this build exposes follows from the build's own version, so a
15
18
  // released a1 cannot be argued into offering the development profiles.
@@ -18,8 +21,8 @@ const capabilities = cliCapabilities(JSON.parse(await readFile(new URL("package.
18
21
  process.exitCode = await dispatchCli(process.argv.slice(2), {
19
22
  launch: async intent => {
20
23
  const [{ prepareInteractiveLaunch }, { runBootstrap }, { healModuleIdentityAtLaunch, releaseCopyIsLaunchable }] = await Promise.all([
21
- import("../dist/features/launch/index.js"),
22
- import("../dist/foundation/release/index.js"),
24
+ import("../dist/features/launch/prepare-launch.js"),
25
+ import("../dist/foundation/release/bootstrap.js"),
23
26
  import("./module-identity.js"),
24
27
  ]);
25
28
  // Compatibility: self-heal the installed tree before the release store copies it: npm 12
@@ -36,18 +39,20 @@ process.exitCode = await dispatchCli(process.argv.slice(2), {
36
39
  });
37
40
  },
38
41
  version: async () => {
39
- const { runVersionStats } = await import("../dist/cli/index.js");
42
+ const { runVersionStats } = await import("../dist/cli/version-stats.js");
40
43
  return await runVersionStats({ packageRoot: fileURLToPath(packageRoot) });
41
44
  },
42
45
  update: async (channel, target) => {
43
- const { runSelfUpdate } = await import("../dist/foundation/release/index.js");
46
+ const { runSelfUpdate } = await import("../dist/foundation/release/update.js");
44
47
  return await runSelfUpdate({ packageRoot: fileURLToPath(packageRoot), channel, ...(target === undefined ? {} : { target }) });
45
48
  },
46
49
  packages: async request => {
47
- const [{ runPackageCommand }, { createPiPackagesPort }] = await Promise.all([
48
- import("../dist/cli/index.js"),
49
- import("../dist/integrations/pi/engine/index.js"),
50
+ const [{ runPackageCommand }, identity] = await Promise.all([
51
+ import("../dist/cli/packages.js"),
52
+ import("./module-identity.js"),
50
53
  ]);
54
+ identity.configurePinnedPiPublicPackage(fileURLToPath(packageRoot));
55
+ const { createPiPackagesPort } = await import("../dist/integrations/pi/engine/package-integration.js");
51
56
  return await runPackageCommand(request, { createPort: createPiPackagesPort });
52
57
  },
53
58
  }, {
package/bin/guardian.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const startup = await import("../dist/foundation/startup/index.js");
3
+ const startup = await import("../dist/foundation/startup/startup-runtime.js");
4
4
  startup.enableEnvironmentCompileCache(process.env);
5
5
  await startup.markStartupPhase(process.env, "guardian-start");
6
6
  const { resolve } = await import("node:path");
7
- const { parseSessionSelection } = await import("../dist/foundation/lifecycle/index.js");
8
- const { runLaunchGuardian } = await import("../dist/foundation/launch-guardian/index.js");
7
+ const { parseSessionSelection } = await import("../dist/foundation/lifecycle/session-selection.js");
8
+ const { runLaunchGuardian } = await import("../dist/foundation/launch-guardian/main.js");
9
9
 
10
10
  const { readLaunchContext } = await import("../dist/foundation/launch-context/index.js");
11
11
  const launchContext = readLaunchContext(process.env, "release");
@@ -88,12 +88,30 @@ function followProxyReExport(resolvedPath) {
88
88
  * require resolves pi-tui, which publishes no `exports` map at all.
89
89
  */
90
90
  export function resolvePinnedPiTui(packageRoot) {
91
+ const pinnedRoot = resolvePinnedPiRoot(packageRoot);
92
+ return canonical(createRequire(pathToFileURL(join(pinnedRoot, "package.json")).href).resolve("@earendil-works/pi-tui"));
93
+ }
94
+
95
+ /**
96
+ * Bind Pi's documented package-asset override to the exact public package root.
97
+ * The generated startup facade otherwise inherits its own A1 `import.meta.url`,
98
+ * which would make Pi read A1's package metadata and themes.
99
+ */
100
+ export function configurePinnedPiPublicPackage(packageRoot, environment = process.env) {
101
+ const pinnedRoot = resolvePinnedPiRoot(packageRoot);
102
+ const manifest = JSON.parse(readFileSync(join(pinnedRoot, "package.json"), "utf8"));
103
+ if (manifest.name !== "@earendil-works/pi-coding-agent" || typeof manifest.version !== "string") {
104
+ throw new Error("pinned Pi public package identity is invalid");
105
+ }
106
+ environment.PI_PACKAGE_DIR = pinnedRoot;
107
+ return { root: pinnedRoot, version: manifest.version };
108
+ }
109
+
110
+ function resolvePinnedPiRoot(packageRoot) {
91
111
  let directory = packageRoot;
92
112
  while (true) {
93
113
  const candidate = join(directory, "node_modules", "@earendil-works", "pi-coding-agent");
94
- if (existsSync(join(candidate, "package.json"))) {
95
- return canonical(createRequire(pathToFileURL(join(candidate, "package.json")).href).resolve("@earendil-works/pi-tui"));
96
- }
114
+ if (existsSync(join(candidate, "package.json"))) return canonical(candidate);
97
115
  const parent = dirname(directory);
98
116
  if (parent === directory) throw new Error("pinned Pi is not installed beneath this package root");
99
117
  directory = parent;
@@ -0,0 +1,3 @@
1
+ export function configurePinnedPiPublicPackageEntry(entryUrl: string): { readonly root: string; readonly version: string };
2
+ export function pinnedPiModuleUrl(relativeModule: string): string;
3
+ export function resolvePinnedPiImport(specifier: string): string;
@@ -0,0 +1,74 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, posix, resolve, sep } from "node:path";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
+
5
+ /** Bind the generated facade to the package reached through Pi's public entry. */
6
+ export function configurePinnedPiPublicPackageEntry(entryUrl) {
7
+ const root = dirname(dirname(fileURLToPath(entryUrl)));
8
+ const manifest = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8"));
9
+ if (manifest.name !== "@earendil-works/pi-coding-agent" || typeof manifest.version !== "string") {
10
+ throw new Error("pinned Pi public package identity is invalid");
11
+ }
12
+ process.env.PI_PACKAGE_DIR = root;
13
+ return { root, version: manifest.version };
14
+ }
15
+
16
+ /** Preserve one bundled Pi module's original public-package URL semantics. */
17
+ export function pinnedPiModuleUrl(relativeModule) {
18
+ if (typeof relativeModule !== "string" || relativeModule.includes("\\") || relativeModule.startsWith("/")
19
+ || posix.normalize(relativeModule).startsWith("../") || !relativeModule.endsWith(".js")) {
20
+ throw new Error("pinned Pi module identity is invalid");
21
+ }
22
+ return pathToFileURL(resolve(pinnedRoot(), "dist", relativeModule)).href;
23
+ }
24
+
25
+ /** Resolve documented Pi dependency exports from the pinned public package tree. */
26
+ export function resolvePinnedPiImport(specifier) {
27
+ if (typeof specifier !== "string" || specifier.includes("\\") || specifier.includes("\0")) {
28
+ throw new Error("pinned Pi import specifier is invalid");
29
+ }
30
+ const root = pinnedRoot();
31
+ if (specifier === "@earendil-works/pi-coding-agent") return pathToFileURL(resolve(root, "dist", "index.js")).href;
32
+ const match = /^(@[^/]+\/[^/]+|[^/]+)(\/.*)?$/.exec(specifier);
33
+ if (match === null) throw new Error(`pinned Pi import is invalid: ${specifier}`);
34
+ const packageName = match[1];
35
+ const subpath = match[2]?.slice(1) ?? "";
36
+ const packageRoot = resolve(root, "node_modules", ...packageName.split("/"));
37
+ const manifestPath = resolve(packageRoot, "package.json");
38
+ if (!existsSync(manifestPath)) throw new Error(`pinned Pi dependency is unavailable: ${packageName}`);
39
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
40
+ const declaredTarget = exportedTarget(manifest.exports, subpath) ?? (subpath.length === 0 ? manifest.module ?? manifest.main : undefined);
41
+ const target = typeof declaredTarget === "string" && !declaredTarget.startsWith(".") ? `./${declaredTarget}` : declaredTarget;
42
+ if (typeof target !== "string" || !target.startsWith("./")) throw new Error(`pinned Pi dependency export is unavailable: ${specifier}`);
43
+ const path = resolve(packageRoot, target);
44
+ if (!path.startsWith(`${packageRoot}${sep}`) || !existsSync(path)) throw new Error(`pinned Pi dependency export is missing: ${specifier}`);
45
+ return pathToFileURL(path).href;
46
+ }
47
+
48
+ function pinnedRoot() {
49
+ const root = process.env.PI_PACKAGE_DIR;
50
+ if (!root || !existsSync(resolve(root, "package.json"))) throw new Error("pinned Pi public package directory is not configured");
51
+ return resolve(root);
52
+ }
53
+
54
+ function exportedTarget(exports, subpath) {
55
+ const key = subpath.length === 0 ? "." : `./${subpath}`;
56
+ if (typeof exports === "string") return subpath.length === 0 ? exports : undefined;
57
+ if (!exports || typeof exports !== "object") return undefined;
58
+ const exact = exports[key];
59
+ if (exact !== undefined) return importCondition(exact);
60
+ for (const [pattern, value] of Object.entries(exports)) {
61
+ const star = pattern.indexOf("*");
62
+ if (star < 0 || !key.startsWith(pattern.slice(0, star)) || !key.endsWith(pattern.slice(star + 1))) continue;
63
+ const matched = key.slice(star, key.length - (pattern.length - star - 1));
64
+ const target = importCondition(value);
65
+ return typeof target === "string" ? target.replaceAll("*", matched) : undefined;
66
+ }
67
+ return undefined;
68
+ }
69
+
70
+ function importCondition(value) {
71
+ if (typeof value === "string") return value;
72
+ if (!value || typeof value !== "object") return undefined;
73
+ return importCondition(value.import ?? value.default);
74
+ }
package/bin/ui.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const startup = await import("../dist/foundation/startup/index.js");
3
+ const startup = await import("../dist/foundation/startup/startup-runtime.js");
4
4
  startup.enableEnvironmentCompileCache(process.env);
5
5
  const { readLaunchContext } = await import("../dist/foundation/launch-context/index.js");
6
6
  const launchContext = readLaunchContext(process.env, "profile");
7
7
  const profile = launchContext.launchProfile;
8
- const { installFatalExit } = await import("../dist/foundation/terminal-cleanup/index.js");
9
- const { resolveProductPaths } = await import("../dist/foundation/lifecycle/index.js");
8
+ const { installFatalExit } = await import("../dist/foundation/terminal-cleanup/fatal-exit.js");
9
+ const { resolveProductPaths } = await import("../dist/foundation/lifecycle/paths.js");
10
10
  const { join } = await import("node:path");
11
11
  let runningApplication;
12
12
  const fatal = profile === "a1" ? installFatalExit({
@@ -14,33 +14,29 @@ const fatal = profile === "a1" ? installFatalExit({
14
14
  releaseId: launchContext.releaseId,
15
15
  dispose: () => runningApplication?.dispose(),
16
16
  }) : undefined;
17
- // Performance: begin the exact launch graph together while the trace write is pending.
18
- // Direct owned-module entries avoid evaluating unrelated barrel exports before first paint.
19
- const modules = Promise.all([
20
- import("./module-identity.js"),
17
+ const [{ fileURLToPath }, identity, descriptor] = await Promise.all([
21
18
  import("node:url"),
22
- import("../dist/features/launch/runtime-selection.js"),
23
- import("../dist/foundation/lifecycle/session-selection.js"),
24
- import("../dist/features/owned-ui/project-trust-prompt.js"),
25
- import("../dist/features/owned-ui/session-fork-prompt.js"),
26
- import("../dist/features/owned-ui/run.js"),
27
- import("../dist/composition/owned-ui.js"),
19
+ import("./module-identity.js"),
20
+ import("../dist/foundation/startup/startup-descriptor.js"),
28
21
  ]);
22
+ const packageRootPath = fileURLToPath(new URL("..", import.meta.url));
23
+ identity.configurePinnedPiPublicPackage(packageRootPath);
24
+ // Performance: begin the exact launch graph together while the trace write is pending.
25
+ // Direct owned-module entries avoid evaluating unrelated barrel exports before first paint.
26
+ const modules = descriptor.loadDeclaredStartupGraph();
29
27
  await startup.markStartupPhase(process.env, "ui-entry");
30
28
  const [
31
- { assertSinglePiTuiModuleAtLaunch },
32
- { fileURLToPath },
33
29
  { runSelectedInteractiveRuntime },
34
30
  { parseSessionSelection },
35
31
  { createConsoleProjectTrustPrompt },
36
32
  { createConsoleSessionForkPrompt },
37
- { runOwnedUi },
33
+ { runOwnedUi, terminateOwnedUiProcess },
38
34
  { composeOwnedUi },
39
35
  ] = await modules;
40
36
 
41
37
  // Compatibility: before the composition uses pinned Pi's terminal stack: confirm A1 and Pi
42
38
  // resolve it to the same copy, so extensions and the owned UI share one module identity.
43
- assertSinglePiTuiModuleAtLaunch(fileURLToPath(new URL("..", import.meta.url)), message => process.stderr.write(message));
39
+ identity.assertSinglePiTuiModuleAtLaunch(packageRootPath, message => process.stderr.write(message));
44
40
  await startup.markStartupPhase(process.env, "ui-modules-loaded");
45
41
 
46
42
  const sessionSelection = parseSessionSelection(process.argv.slice(2));
@@ -61,7 +57,10 @@ Promise.resolve().then(() => {
61
57
  },
62
58
  });
63
59
  }).then(
64
- code => { fatal?.remove(); process.exitCode = code; },
60
+ code => {
61
+ fatal?.remove();
62
+ return terminateOwnedUiProcess(code);
63
+ },
65
64
  error => {
66
65
  if (fatal && error?.name !== "PiSessionSelectionError") { fatal.fail(error); return; }
67
66
  fatal?.remove();
package/bin/warmup.js CHANGED
@@ -1,21 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const startup = await import("../dist/foundation/startup/index.js");
3
+ const startup = await import("../dist/foundation/startup/startup-runtime.js");
4
4
  startup.assertImmutableWarmupEnvironment(process.env);
5
5
  startup.enableEnvironmentCompileCache(process.env);
6
+ const [{ fileURLToPath }, identity, descriptor] = await Promise.all([
7
+ import("node:url"),
8
+ import("./module-identity.js"),
9
+ import("../dist/foundation/startup/startup-descriptor.js"),
10
+ ]);
11
+ identity.configurePinnedPiPublicPackage(fileURLToPath(new URL("..", import.meta.url)));
6
12
 
7
13
  // Security: this entry imports the exact interactive graph but never composes it, so it
8
14
  // creates no terminal, profile/session path, trust callback, executable resource loader,
9
15
  // 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"),
18
- ]);
16
+ const [launch, selection, trustPrompt, forkPrompt, run, composition] = await descriptor.loadDeclaredStartupGraph();
19
17
  if (typeof identity.assertSinglePiTuiModuleAtLaunch !== "function"
20
18
  || typeof launch.runSelectedInteractiveRuntime !== "function"
21
19
  || typeof selection.parseSessionSelection !== "function"
@@ -1,6 +1,8 @@
1
- import { type SessionSelection } from "../foundation/lifecycle/index.js";
2
- import { type PiEngineAdapter, type PiProjectTrustPreflightPrompt, type PiSessionForkPrompt } from "../integrations/pi/engine/index.js";
3
- import { OwnedUiSettingsSession } from "../ui/settings/index.js";
1
+ import type { SessionSelection } from "../foundation/lifecycle/session-selection.js";
2
+ import type { PiEngineAdapter } from "../integrations/pi/engine/adapter.js";
3
+ import type { PiProjectTrustPreflightPrompt } from "../integrations/pi/engine/project-trust-preflight.js";
4
+ import type { PiSessionForkPrompt } from "../integrations/pi/engine/session-selection.js";
5
+ import { OwnedUiSettingsSession } from "../ui/settings/session.js";
4
6
  import type { OwnedUiApplicationPort, PresentationTerminalPort } from "../contracts/presentation/index.js";
5
7
  export interface OwnedUiCompositionOptions {
6
8
  readonly cwd?: string;
@@ -1,13 +1,17 @@
1
- import { SuggestionDiagnosticCapture } from "../features/prompt-suggestions/index.js";
1
+ import { SuggestionDiagnosticCapture } from "../features/prompt-suggestions/diagnostics.js";
2
2
  import { PRODUCT_IDENTITY } from "../product-identity.js";
3
- import { PromptHistoryService, PromptImageSidecar, resolvePromptHistoryPath } from "../features/prompt-history/index.js";
4
- import { resolvePromptHistoryDataDir } from "../features/launch/index.js";
5
- import { resolveProductPaths } from "../foundation/lifecycle/index.js";
6
- import { applyConfiguredPiTheme, getAvailablePiThemes, loadHistoryEditor } from "../integrations/pi/components/index.js";
7
- import { createPiEngineAdapter, } from "../integrations/pi/engine/index.js";
8
- import { ClipboardDiagnosticCapture, OwnedUiSessionShell } from "../integrations/pi/session-ui/index.js";
9
- import { OwnedUiSettingsSession, OwnedUiSettingsStore } from "../ui/settings/index.js";
10
- import { createPiTerminalBridge } from "../integrations/pi/tui-runtime/index.js";
3
+ import { PromptHistoryService } from "../features/prompt-history/service.js";
4
+ import { PromptImageSidecar } from "../features/prompt-history/image-sidecar.js";
5
+ import { resolvePromptHistoryPath } from "../features/prompt-history/paths.js";
6
+ import { resolvePromptHistoryDataDir } from "../features/launch/profile-paths.js";
7
+ import { resolveProductPaths } from "../foundation/lifecycle/paths.js";
8
+ import { applyConfiguredPiTheme, getAvailablePiThemes } from "../integrations/pi/components/upstream/theme/theme.js";
9
+ import { createPiEngineAdapter } from "../integrations/pi/engine/adapter.js";
10
+ import { ClipboardDiagnosticCapture } from "../integrations/pi/session-ui/clipboard-diagnostics.js";
11
+ import { OwnedUiSessionShell } from "../integrations/pi/session-ui/session-shell.js";
12
+ import { OwnedUiSettingsSession } from "../ui/settings/session.js";
13
+ import { OwnedUiSettingsStore } from "../ui/settings/store.js";
14
+ import { createPiTerminalBridge } from "../integrations/pi/tui-runtime/presentation-adapter.js";
11
15
  import { createOwnedRouteHost } from "./settings-route-host.js";
12
16
  export async function composeOwnedUiApplication(options = {}) {
13
17
  return (await composeOwnedUi(options)).application;
@@ -81,7 +85,10 @@ export async function composeOwnedUi(options = {}) {
81
85
  }),
82
86
  ...(viewportSettings === null ? {} : { viewportSettings }),
83
87
  ...(promptSuggestions === null ? {} : { promptSuggestions }),
84
- ...(promptHistory === null ? {} : { promptHistory: { ...promptHistory, editor: await loadHistoryEditor() } }),
88
+ ...(promptHistory === null ? {} : { promptHistory: {
89
+ ...promptHistory,
90
+ editor: await import("../integrations/pi/components/history-editor-loader.js").then(module => module.loadHistoryEditor()),
91
+ } }),
85
92
  });
86
93
  }
87
94
  catch (error) {
@@ -1,8 +1,7 @@
1
- import type { OwnedUiSettingsSession } from "../ui/settings/index.js";
2
- import { type UiRouteHost } from "../ui/apps/index.js";
1
+ import type { OwnedUiSettingsSession } from "../ui/settings/session.js";
2
+ import type { UiRouteHost } from "../ui/apps/contracts.js";
3
3
  /**
4
- * Registers the A1-owned screens and adapts the app host to the shell's route
5
- * seam. The shell mounts whatever surface it is handed and forwards keys; it
6
- * never learns what an app is.
4
+ * Declares the A1-owned settings route without evaluating its presentation graph
5
+ * during startup. Opening the route retains input while the optional module loads.
7
6
  */
8
7
  export declare function createOwnedRouteHost(settings: OwnedUiSettingsSession): UiRouteHost;
@@ -1,59 +1,100 @@
1
- import { SETTINGS_APP_ID, SETTINGS_ROUTE, SettingsApp } from "../features/owned-ui/index.js";
2
- import { piTheme } from "../integrations/pi/components/index.js";
3
- import { UiAppHost, UiAppRegistry, } from "../ui/apps/index.js";
4
- import { faint } from "../ui/components/index.js";
1
+ import { SETTINGS_APP_ID, SETTINGS_ROUTE } from "../features/owned-ui/settings-route.js";
2
+ import { piTheme } from "../integrations/pi/components/upstream/theme/theme.js";
3
+ import { faint } from "../ui/components/text.js";
5
4
  /**
6
- * Registers the A1-owned screens and adapts the app host to the shell's route
7
- * seam. The shell mounts whatever surface it is handed and forwards keys; it
8
- * never learns what an app is.
5
+ * Declares the A1-owned settings route without evaluating its presentation graph
6
+ * during startup. Opening the route retains input while the optional module loads.
9
7
  */
10
8
  export function createOwnedRouteHost(settings) {
9
+ return {
10
+ claims: route => route === SETTINGS_ROUTE,
11
+ open: route => route === SETTINGS_ROUTE ? deferredSettingsSurface(settings) : null,
12
+ };
13
+ }
14
+ function deferredSettingsSurface(settings) {
15
+ let delegate = null;
16
+ let closed = false;
17
+ let failure = null;
18
+ let onRender = () => undefined;
19
+ let onExit = () => undefined;
20
+ const pending = [];
21
+ void loadSettingsSurface(settings).then(surface => {
22
+ if (closed) {
23
+ surface.close();
24
+ return;
25
+ }
26
+ delegate = surface;
27
+ surface.onRenderRequested(() => onRender());
28
+ surface.onExitRequested(() => onExit());
29
+ for (const action of pending.splice(0))
30
+ action(surface);
31
+ onRender();
32
+ }).catch(error => {
33
+ failure = `Could not load settings: ${error instanceof Error ? error.message : String(error)}`;
34
+ onRender();
35
+ });
36
+ const defer = (action) => {
37
+ if (delegate !== null)
38
+ action(delegate);
39
+ else if (!closed && pending.length < 32)
40
+ pending.push(action);
41
+ };
42
+ return {
43
+ id: SETTINGS_APP_ID,
44
+ render: (width, height) => delegate?.render(width, height)
45
+ ?? [...(height > 0 ? [failure ?? "Loading settings…"] : []), ...Array(Math.max(0, height - 1)).fill("")]
46
+ .map(line => line.slice(0, Math.max(0, width))),
47
+ handleInput: data => { defer(surface => surface.handleInput(data)); return true; },
48
+ handleMouse: event => { defer(surface => surface.handleMouse(event)); return true; },
49
+ isClosed: () => closed || delegate?.isClosed() === true,
50
+ close: () => { closed = true; pending.length = 0; delegate?.close(); },
51
+ onRenderRequested: listener => { onRender = listener; },
52
+ onExitRequested: listener => { onExit = listener; },
53
+ };
54
+ }
55
+ async function loadSettingsSurface(settings) {
56
+ const [{ SettingsApp }, { UiAppHost }, { UiAppRegistry }] = await Promise.all([
57
+ import("../features/owned-ui/settings-app.js"),
58
+ import("../ui/apps/host.js"),
59
+ import("../ui/apps/registry.js"),
60
+ ]);
11
61
  const registry = new UiAppRegistry();
12
62
  registry.register({ id: SETTINGS_APP_ID, route: SETTINGS_ROUTE, create: () => new SettingsApp(settings) });
63
+ let size = { width: 80, height: 24 };
64
+ let frame = [];
65
+ let closed = false;
66
+ let onRender = () => undefined;
67
+ let onExit = () => undefined;
68
+ const host = new UiAppHost({
69
+ registry,
70
+ closeOnInterrupt: true,
71
+ theme: pinnedTheme(),
72
+ surface: {
73
+ size: () => size,
74
+ requestRender: () => onRender(),
75
+ exit: () => onExit(),
76
+ present: lines => {
77
+ if (lines === null)
78
+ closed = true;
79
+ else
80
+ frame = lines;
81
+ },
82
+ },
83
+ });
84
+ host.open(SETTINGS_APP_ID);
13
85
  return {
14
- claims: route => registry.forRoute(route) !== null,
15
- open: route => {
16
- const registration = registry.forRoute(route);
17
- if (registration === null)
18
- return null;
19
- let size = { width: 80, height: 24 };
20
- let frame = [];
21
- let closed = false;
22
- let onRender = () => undefined;
23
- let onExit = () => undefined;
24
- const host = new UiAppHost({
25
- registry,
26
- closeOnInterrupt: true,
27
- theme: pinnedTheme(),
28
- surface: {
29
- size: () => size,
30
- requestRender: () => onRender(),
31
- exit: () => onExit(),
32
- present: lines => {
33
- if (lines === null)
34
- closed = true;
35
- else
36
- frame = lines;
37
- },
38
- },
39
- });
40
- host.open(registration.id);
41
- const surface = {
42
- id: registration.id,
43
- render: (width, height) => {
44
- size = { width, height };
45
- host.render();
46
- return frame.length === height ? frame : [...frame.slice(0, height), ...Array(Math.max(0, height - frame.length)).fill("")];
47
- },
48
- handleInput: data => host.handleInput(data).consumed,
49
- handleMouse: event => host.handleMouse(event).consumed,
50
- isClosed: () => closed || !host.isPresenting,
51
- close: () => host.close(),
52
- onRenderRequested: listener => { onRender = listener; },
53
- onExitRequested: listener => { onExit = listener; },
54
- };
55
- return surface;
86
+ id: SETTINGS_APP_ID,
87
+ render: (width, height) => {
88
+ size = { width, height };
89
+ host.render();
90
+ return frame.length === height ? frame : [...frame.slice(0, height), ...Array(Math.max(0, height - frame.length)).fill("")];
56
91
  },
92
+ handleInput: data => host.handleInput(data).consumed,
93
+ handleMouse: event => host.handleMouse(event).consumed,
94
+ isClosed: () => closed || !host.isPresenting,
95
+ close: () => host.close(),
96
+ onRenderRequested: listener => { onRender = listener; },
97
+ onExitRequested: listener => { onExit = listener; },
57
98
  };
58
99
  }
59
100
  /** Maps A1 UI tokens onto the pinned Pi theme so owned screens match the shell. */
@@ -61,14 +102,8 @@ function pinnedTheme() {
61
102
  return {
62
103
  fg: (token, text) => piTheme().fg(token, text),
63
104
  bold: (text) => piTheme().bold(text),
64
- // Rationale: unpainted, so a list reads against the terminal the reader actually has
65
- // rather than against the background the configured theme was built for.
66
105
  plain: (text) => text,
67
- // Rationale: quiet, and quieter again: the terminal's own faint attribute over the
68
- // dimmest colour the theme has.
69
106
  disabled: (text) => faint(piTheme().fg("dim", text)),
70
- // Rationale: physical review requires the shared value menu to read as a floating
71
- // product surface instead of blending into the settings rows behind it.
72
107
  highlight: (text) => `\u001b[48;2;82;82;82m\u001b[97m${text}\u001b[39m\u001b[49m`,
73
108
  panel: (text) => `\u001b[48;2;55;55;55m${text}\u001b[49m`,
74
109
  };
@@ -1,6 +1,8 @@
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";
1
+ import { type SessionSelection } from "../../foundation/lifecycle/session-selection.js";
2
+ import type { LaunchProfileId } from "../../foundation/lifecycle/model.js";
3
+ export { parseSessionSelection } from "../../foundation/lifecycle/session-selection.js";
4
+ export type { SessionSelection } from "../../foundation/lifecycle/session-selection.js";
5
+ export type { LaunchProfileId } from "../../foundation/lifecycle/model.js";
4
6
  export interface InteractiveLaunchIntent {
5
7
  readonly kind: "interactive";
6
8
  readonly profileId: LaunchProfileId;
@@ -1,5 +1,5 @@
1
- import { sessionSelectionArguments } from "../../foundation/lifecycle/index.js";
2
- export { parseSessionSelection } from "../../foundation/lifecycle/index.js";
1
+ import { sessionSelectionArguments } from "../../foundation/lifecycle/session-selection.js";
2
+ export { parseSessionSelection } from "../../foundation/lifecycle/session-selection.js";
3
3
  export function interactiveLaunchIntent(profileId, sessionSelection) {
4
4
  if (sessionSelection !== undefined && profileId !== "a1")
5
5
  throw new Error("session selection requires the normal A1 profile");
@@ -1,7 +1,7 @@
1
1
  import { homedir } from "node:os";
2
2
  import { posix, win32 } from "node:path";
3
3
  import { PRODUCT_IDENTITY } from "../../product-identity.js";
4
- import { resolveProductPaths } from "../../foundation/lifecycle/index.js";
4
+ import { resolveProductPaths } from "../../foundation/lifecycle/paths.js";
5
5
  export function resolveLaunchProfilePaths(options = {}) {
6
6
  const environment = options.environment ?? process.env;
7
7
  const path = options.platform === "win32" || (options.platform === undefined && process.platform === "win32") ? win32 : posix;
@@ -1,4 +1,4 @@
1
- import { type LaunchProfileId } from "../../foundation/lifecycle/index.js";
1
+ import { type LaunchProfileId } from "../../foundation/lifecycle/model.js";
2
2
  /**
3
3
  * Both interactive commands use the owned composition. Bare A1 enables product
4
4
  * surfaces; the Pi comparison command withholds them and reads Pi's own profile.
@@ -1,4 +1,4 @@
1
- import { assertLaunchProfileId } from "../../foundation/lifecycle/index.js";
1
+ import { assertLaunchProfileId } from "../../foundation/lifecycle/model.js";
2
2
  export function selectInteractiveRuntime(profileId) {
3
3
  assertLaunchProfileId(profileId);
4
4
  return Object.freeze({
@@ -8,4 +8,10 @@ export interface OwnedUiRunOptions {
8
8
  */
9
9
  readonly settings?: OwnedUiSettingsSession;
10
10
  }
11
+ /**
12
+ * Completes the interactive executable only after runOwnedUi has restored its terminal.
13
+ * Extensions are untrusted process guests and can retain servers or timers after session
14
+ * shutdown, so assigning exitCode alone cannot guarantee return to the invoking shell.
15
+ */
16
+ export declare function terminateOwnedUiProcess(code: number): Promise<never>;
11
17
  export declare function runOwnedUi(options: OwnedUiRunOptions): Promise<number>;
@@ -1,5 +1,16 @@
1
- import { markStartupPhase } from "../../foundation/startup/index.js";
2
- import { boundedCleanup } from "../../foundation/terminal-cleanup/index.js";
1
+ import { markStartupPhase } from "../../foundation/startup/startup-runtime.js";
2
+ import { boundedCleanup } from "../../foundation/terminal-cleanup/terminal-reset.js";
3
+ const PROCESS_OUTPUT_FLUSH_TIMEOUT_MS = 250;
4
+ /**
5
+ * Completes the interactive executable only after runOwnedUi has restored its terminal.
6
+ * Extensions are untrusted process guests and can retain servers or timers after session
7
+ * shutdown, so assigning exitCode alone cannot guarantee return to the invoking shell.
8
+ */
9
+ export async function terminateOwnedUiProcess(code) {
10
+ process.exitCode = code;
11
+ await Promise.all([flushProcessOutput(process.stdout), flushProcessOutput(process.stderr)]);
12
+ return process.exit(code);
13
+ }
3
14
  export async function runOwnedUi(options) {
4
15
  const { application, settings } = options;
5
16
  let failed = false;
@@ -30,3 +41,24 @@ export async function runOwnedUi(options) {
30
41
  }
31
42
  }
32
43
  }
44
+ async function flushProcessOutput(stream) {
45
+ if (stream.destroyed || !stream.writable)
46
+ return;
47
+ await new Promise(resolve => {
48
+ let settled = false;
49
+ const finish = () => {
50
+ if (settled)
51
+ return;
52
+ settled = true;
53
+ clearTimeout(timeout);
54
+ resolve();
55
+ };
56
+ const timeout = setTimeout(finish, PROCESS_OUTPUT_FLUSH_TIMEOUT_MS);
57
+ try {
58
+ stream.write("", finish);
59
+ }
60
+ catch {
61
+ finish();
62
+ }
63
+ });
64
+ }