@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.
- package/bin/cli.js +14 -9
- package/bin/guardian.js +3 -3
- package/bin/module-identity.js +21 -3
- package/bin/pinned-pi-public.d.ts +3 -0
- package/bin/pinned-pi-public.js +74 -0
- package/bin/ui.js +17 -18
- package/bin/warmup.js +8 -10
- package/dist/composition/owned-ui.d.ts +5 -3
- package/dist/composition/owned-ui.js +17 -10
- package/dist/composition/settings-route-host.d.ts +4 -5
- package/dist/composition/settings-route-host.js +90 -55
- package/dist/features/launch/intent.d.ts +5 -3
- package/dist/features/launch/intent.js +2 -2
- package/dist/features/launch/profile-paths.js +1 -1
- package/dist/features/launch/runtime-selection.d.ts +1 -1
- package/dist/features/launch/runtime-selection.js +1 -1
- package/dist/features/owned-ui/run.d.ts +6 -0
- package/dist/features/owned-ui/run.js +34 -2
- package/dist/features/owned-ui/settings-app.d.ts +1 -3
- package/dist/features/owned-ui/settings-app.js +2 -2
- package/dist/features/owned-ui/settings-route.d.ts +2 -0
- package/dist/features/owned-ui/settings-route.js +2 -0
- package/dist/foundation/release/update.js +5 -3
- package/dist/foundation/startup/index.d.ts +1 -0
- package/dist/foundation/startup/index.js +1 -0
- package/dist/foundation/startup/startup-budget.d.ts +46 -0
- package/dist/foundation/startup/startup-budget.js +42 -0
- package/dist/foundation/startup/startup-descriptor.js +34 -0
- package/dist/foundation/startup/startup-runtime.d.ts +0 -9
- package/dist/foundation/startup/startup-runtime.js +0 -14
- package/dist/foundation/terminal-cleanup/fatal-exit.js +4 -2
- package/dist/integrations/pi/components/components.js +1 -1
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +1 -1
- package/dist/integrations/pi/components/owned-editor-ux.js +6 -2
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +2 -2
- package/dist/integrations/pi/components/shell-extension-ui.d.ts +1 -1
- package/dist/integrations/pi/components/shell-extension-ui.js +1 -1
- package/dist/integrations/pi/components/shell-footer-status.js +1 -1
- package/dist/integrations/pi/components/shell-presenters-info.js +1 -1
- package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +2 -2
- package/dist/integrations/pi/components/shell-presenters-transcript.js +2 -2
- package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +1 -1
- package/dist/integrations/pi/components/shell-selectors-dialogs.js +1 -1
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +1 -1
- package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +2 -0
- package/dist/integrations/pi/components/submitted-prompt-adapter.js +8 -2
- package/dist/integrations/pi/components/tool-image-presentation.d.ts +1 -1
- package/dist/integrations/pi/components/tool-image-presentation.js +1 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +1 -1
- package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/components/first-time-setup.js +1 -1
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/status-indicator.js +1 -1
- package/dist/integrations/pi/components/upstream/components/tool-execution.js +1 -1
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme.js +1 -1
- package/dist/integrations/pi/engine/adapter.d.ts +1 -1
- package/dist/integrations/pi/engine/adapter.js +1 -1
- package/dist/integrations/pi/engine/model-auth-integration.d.ts +1 -1
- package/dist/integrations/pi/engine/package-integration.js +1 -1
- package/dist/integrations/pi/engine/project-trust-preflight.js +1 -1
- package/dist/integrations/pi/engine/resource-extension-integration.d.ts +1 -1
- package/dist/integrations/pi/engine/runtime-integration.d.ts +1 -1
- package/dist/integrations/pi/engine/runtime-integration.js +2 -2
- package/dist/integrations/pi/engine/session-integration.d.ts +1 -1
- package/dist/integrations/pi/engine/session-selection.d.ts +1 -1
- package/dist/integrations/pi/engine/session-selection.js +1 -1
- package/dist/integrations/pi/engine/settings-integration.d.ts +1 -1
- package/dist/integrations/pi/engine/windows-filesystem-hygiene.d.ts +1 -1
- package/dist/integrations/pi/engine/windows-filesystem-hygiene.js +1 -1
- package/dist/integrations/pi/session-ui/clipboard-diagnostics.d.ts +1 -1
- package/dist/integrations/pi/session-ui/clipboard-diagnostics.js +1 -1
- package/dist/integrations/pi/session-ui/paste-executor.d.ts +1 -1
- package/dist/integrations/pi/session-ui/paste-executor.js +1 -1
- package/dist/integrations/pi/session-ui/paste-protocol.d.ts +1 -1
- package/dist/integrations/pi/session-ui/prompt-chips.d.ts +1 -1
- package/dist/integrations/pi/session-ui/prompt-chips.js +1 -1
- package/dist/integrations/pi/session-ui/response-copy-coordinator.d.ts +1 -1
- package/dist/integrations/pi/session-ui/response-copy-coordinator.js +1 -1
- package/dist/integrations/pi/session-ui/response-copy-transport.d.ts +1 -1
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +8 -6
- package/dist/integrations/pi/session-ui/session-shell-root.js +31 -10
- package/dist/integrations/pi/session-ui/session-shell.d.ts +6 -3
- package/dist/integrations/pi/session-ui/session-shell.js +54 -11
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +8 -3
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +33 -5
- package/dist/integrations/pi/session-ui/system-clipboard.d.ts +1 -1
- package/dist/integrations/pi/session-ui/system-clipboard.js +7 -4
- package/dist/integrations/pi/startup-public.d.ts +2 -0
- package/dist/integrations/pi/startup-public.js +2128 -0
- package/dist/integrations/pi/startup-public.manifest.json +11696 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +1 -1
- 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/runtime-payload-inventory.json +4 -1
- package/docs/architecture/response-copy-delivery.md +3 -1
- package/docs/ci-release-runbook.md +20 -7
- package/docs/local-worktree-cleanup.md +48 -13
- package/docs/openspec-archive-automation.md +191 -73
- package/docs/validation.md +79 -0
- 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/
|
|
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
|
|
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/
|
|
22
|
-
import("../dist/foundation/release/
|
|
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/
|
|
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/
|
|
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 },
|
|
48
|
-
import("../dist/cli/
|
|
49
|
-
import("
|
|
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/
|
|
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/
|
|
8
|
-
const { runLaunchGuardian } = await import("../dist/foundation/launch-guardian/
|
|
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");
|
package/bin/module-identity.js
CHANGED
|
@@ -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,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/
|
|
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/
|
|
9
|
-
const { resolveProductPaths } = await import("../dist/foundation/lifecycle/
|
|
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
|
-
|
|
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("
|
|
23
|
-
import("../dist/foundation/
|
|
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(
|
|
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 => {
|
|
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/
|
|
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 [
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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/
|
|
1
|
+
import { SuggestionDiagnosticCapture } from "../features/prompt-suggestions/diagnostics.js";
|
|
2
2
|
import { PRODUCT_IDENTITY } from "../product-identity.js";
|
|
3
|
-
import { PromptHistoryService
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
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: {
|
|
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/
|
|
2
|
-
import {
|
|
1
|
+
import type { OwnedUiSettingsSession } from "../ui/settings/session.js";
|
|
2
|
+
import type { UiRouteHost } from "../ui/apps/contracts.js";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
|
2
|
-
import { piTheme } from "../integrations/pi/components/
|
|
3
|
-
import {
|
|
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
|
-
*
|
|
7
|
-
*
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
2
|
-
|
|
3
|
-
export
|
|
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/
|
|
2
|
-
export { parseSessionSelection } from "../../foundation/lifecycle/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
2
|
-
import { boundedCleanup } from "../../foundation/terminal-cleanup/
|
|
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
|
+
}
|