@timurproko/a1 0.1.8-dev.157 → 0.1.8-dev.214
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/bin/cli.js +2 -2
- package/bin/sync-pi-tui-proxy.js +1 -1
- package/bin/ui.js +18 -3
- package/dist/cli/dispatch.js +3 -3
- package/dist/cli/packages.js +1 -1
- package/dist/cli/version-stats.js +3 -3
- package/dist/composition/owned-ui.d.ts +4 -1
- package/dist/composition/owned-ui.js +9 -6
- package/dist/composition/settings-route-host.js +6 -4
- package/dist/contracts/agent-engine/capability-ports.d.ts +2 -2
- package/dist/contracts/agent-engine/domain-validation.js +22 -4
- package/dist/contracts/agent-engine/domain.d.ts +17 -0
- package/dist/contracts/owned-ui/model.d.ts +8 -0
- package/dist/contracts/owned-ui/validation.js +11 -0
- package/dist/features/launch/development-launch.js +1 -1
- package/dist/features/owned-ui/customization.d.ts +2 -0
- package/dist/features/owned-ui/customization.js +2 -0
- package/dist/features/owned-ui/diagnostics.d.ts +1 -0
- package/dist/features/owned-ui/diagnostics.js +1 -0
- package/dist/features/owned-ui/index.d.ts +1 -0
- package/dist/features/owned-ui/index.js +1 -0
- package/dist/features/owned-ui/project-trust-prompt.d.ts +25 -0
- package/dist/features/owned-ui/project-trust-prompt.js +149 -0
- package/dist/features/owned-ui/settings-app.js +69 -44
- package/dist/features/workspace/reducer.d.ts +1 -0
- package/dist/features/workspace/reducer.js +1 -0
- package/dist/features/workspace/router.d.ts +1 -0
- package/dist/features/workspace/router.js +1 -0
- package/dist/features/workspace/store.d.ts +1 -0
- package/dist/features/workspace/store.js +1 -0
- package/dist/foundation/launch-guardian/main.js +1 -1
- package/dist/foundation/native-host-protocol/codec.d.ts +2 -0
- package/dist/foundation/native-host-protocol/codec.js +2 -0
- package/dist/foundation/process-containment/linux-process-inspector.d.ts +1 -0
- package/dist/foundation/process-containment/linux-process-inspector.js +1 -0
- package/dist/foundation/process-containment/native-guardian-containment.d.ts +1 -0
- package/dist/foundation/process-containment/native-guardian-containment.js +1 -0
- package/dist/foundation/process-containment/windows-process-inspector.d.ts +1 -0
- package/dist/foundation/process-containment/windows-process-inspector.js +1 -0
- package/dist/foundation/protocol/client.d.ts +1 -0
- package/dist/foundation/protocol/client.js +1 -0
- package/dist/foundation/protocol/messages.d.ts +1 -0
- package/dist/foundation/protocol/messages.js +1 -0
- package/dist/foundation/release/bootstrap.js +6 -6
- package/dist/foundation/release/cohort-state.d.ts +1 -0
- package/dist/foundation/release/cohort-state.js +1 -0
- package/dist/foundation/release/release-gc.js +1 -1
- package/dist/foundation/release/release.js +1 -1
- package/dist/foundation/release/update-transaction.d.ts +1 -0
- package/dist/foundation/release/update-transaction.js +1 -0
- package/dist/foundation/release/update.js +12 -12
- package/dist/foundation/storage/control-store.d.ts +1 -0
- package/dist/foundation/storage/control-store.js +1 -0
- package/dist/foundation/structured-agent-runtime/backpressure.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/backpressure.js +1 -0
- package/dist/foundation/structured-agent-runtime/commands.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/commands.js +1 -0
- package/dist/foundation/structured-agent-runtime/reconnection.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/reconnection.js +1 -0
- package/dist/foundation/structured-agent-runtime/state.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/state.js +1 -0
- package/dist/foundation/supervision/main.js +1 -1
- package/dist/foundation/supervision/server.d.ts +1 -0
- package/dist/foundation/supervision/server.js +4 -6
- package/dist/integrations/pi/components/conformance.d.ts +1 -0
- package/dist/integrations/pi/components/conformance.js +1 -0
- package/dist/integrations/pi/components/shell-components.d.ts +1 -0
- package/dist/integrations/pi/components/shell-components.js +1 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +10 -0
- package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
- package/dist/integrations/pi/components/shell-footer-status.js +19 -13
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +36 -0
- package/dist/integrations/pi/components/shell-presenters-info.js +78 -0
- package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -36
- package/dist/integrations/pi/components/shell-presenters-transcript.js +88 -128
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +11 -1
- package/dist/integrations/pi/components/shell-shared-facade.js +1 -1
- package/dist/integrations/pi/engine/adapter.d.ts +22 -4
- package/dist/integrations/pi/engine/adapter.js +220 -70
- package/dist/integrations/pi/engine/conformance.d.ts +2 -0
- package/dist/integrations/pi/engine/conformance.js +2 -0
- package/dist/integrations/pi/engine/http-dispatcher.d.ts +4 -0
- package/dist/integrations/pi/engine/http-dispatcher.js +25 -0
- package/dist/integrations/pi/engine/index.d.ts +3 -0
- package/dist/integrations/pi/engine/index.js +3 -0
- package/dist/integrations/pi/engine/model-auth-integration.d.ts +1 -0
- package/dist/integrations/pi/engine/model-auth-integration.js +1 -0
- package/dist/integrations/pi/engine/package-integration.js +1 -1
- package/dist/integrations/pi/engine/project-trust-preflight.d.ts +22 -0
- package/dist/integrations/pi/engine/project-trust-preflight.js +50 -0
- package/dist/integrations/pi/engine/resource-extension-integration.d.ts +1 -0
- package/dist/integrations/pi/engine/resource-extension-integration.js +1 -0
- package/dist/integrations/pi/engine/runtime-integration.d.ts +16 -1
- package/dist/integrations/pi/engine/runtime-integration.js +34 -4
- package/dist/integrations/pi/engine/session-integration.d.ts +1 -0
- package/dist/integrations/pi/engine/session-integration.js +2 -1
- package/dist/integrations/pi/engine/settings-effects.d.ts +66 -0
- package/dist/integrations/pi/engine/settings-effects.js +249 -0
- package/dist/integrations/pi/engine/settings-integration.d.ts +14 -26
- package/dist/integrations/pi/engine/settings-integration.js +102 -108
- package/dist/integrations/pi/engine/workflow-controllers.d.ts +2 -1
- package/dist/integrations/pi/engine/workflow-controllers.js +1 -0
- package/dist/integrations/pi/session-ui/clipboard-image.d.ts +11 -0
- package/dist/integrations/pi/session-ui/clipboard-image.js +29 -0
- package/dist/integrations/pi/session-ui/index.d.ts +1 -0
- package/dist/integrations/pi/session-ui/index.js +1 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +6 -2
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +39 -4
- package/dist/integrations/pi/session-ui/session-shell-root.js +215 -80
- package/dist/integrations/pi/session-ui/session-shell.d.ts +10 -1
- package/dist/integrations/pi/session-ui/session-shell.js +256 -45
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +5 -0
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +76 -11
- package/dist/integrations/pi/session-ui/stream-presentation-coalescer.d.ts +29 -0
- package/dist/integrations/pi/session-ui/stream-presentation-coalescer.js +79 -0
- package/dist/integrations/pi/session-ui/system-clipboard.d.ts +9 -0
- package/dist/integrations/pi/session-ui/system-clipboard.js +38 -9
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +6 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +166 -8
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +25 -0
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +63 -0
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +240 -0
- package/dist/integrations/pi/tui-runtime/index.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/index.js +2 -0
- package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.d.ts +39 -0
- package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.js +139 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/apps/host.js +5 -9
- package/dist/ui/apps/registry.d.ts +1 -0
- package/dist/ui/apps/registry.js +1 -0
- package/dist/ui/components/dialog-panel.js +5 -3
- package/dist/ui/components/frame.d.ts +1 -0
- package/dist/ui/components/frame.js +1 -0
- package/dist/ui/components/index.d.ts +1 -0
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/line-input.d.ts +1 -6
- package/dist/ui/components/line-input.js +4 -9
- package/dist/ui/components/list-view.d.ts +0 -6
- package/dist/ui/components/list-view.js +14 -7
- package/dist/ui/components/progress-status.d.ts +2 -0
- package/dist/ui/components/progress-status.js +4 -0
- package/dist/ui/components/scrollbar.js +1 -1
- package/dist/ui/components/shortcuts.d.ts +1 -0
- package/dist/ui/components/shortcuts.js +1 -0
- package/dist/ui/components/spans.d.ts +2 -5
- package/dist/ui/components/spans.js +28 -8
- package/dist/ui/components/surface.d.ts +0 -5
- package/dist/ui/components/surface.js +1 -5
- package/dist/ui/components/text-selection.d.ts +10 -1
- package/dist/ui/components/text-selection.js +96 -29
- package/dist/ui/components/text.js +3 -3
- package/dist/ui/components/transcript-viewport.d.ts +42 -0
- package/dist/ui/components/transcript-viewport.js +287 -39
- package/dist/ui/settings/sections.d.ts +6 -19
- package/dist/ui/settings/sections.js +17 -15
- package/dist/ui/settings/session.d.ts +8 -18
- package/dist/ui/settings/session.js +60 -52
- package/dist/ui/settings/store.d.ts +1 -0
- package/dist/ui/settings/store.js +2 -1
- package/docs/architecture/code-documentation.md +77 -0
- package/docs/architecture/project-structure.md +15 -1
- package/docs/architecture/ui-reference-provenance.md +23 -3
- package/docs/ci-release-runbook.md +98 -7
- package/docs/manual-owned-ui-checkpoint.md +28 -1
- package/docs/manual-terminal-colour-check.md +4 -3
- package/docs/repository-governance-live-acceptance.md +77 -0
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -78,6 +78,38 @@ npm run test:fast # typecheck + fast suite (alias: npm test)
|
|
|
78
78
|
npm run test:full # complete non-physical suite
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
Create every task worktree at `{working-dir}/.worktrees/<task-id>`, where
|
|
82
|
+
`{working-dir}` is the session's initial working directory. For working directory
|
|
83
|
+
`D:/Git/a1`, use `D:/Git/a1/.worktrees/<task-id>`—never a sibling such as
|
|
84
|
+
`D:/Git/a1-<task-id>`. The primary worktree remains on `develop` for integration only.
|
|
85
|
+
|
|
86
|
+
## Pull request integration
|
|
87
|
+
|
|
88
|
+
Pull requests whose complete diff is only under `openspec/**`, under `docs/**`,
|
|
89
|
+
exactly the root `README.md`, or a combination of those paths are automatically
|
|
90
|
+
squash-merged after `Development validation required` succeeds. The automation
|
|
91
|
+
reads the complete GitHub changed-file list, including both sides of renames, and
|
|
92
|
+
runs only for trusted branches in this repository.
|
|
93
|
+
|
|
94
|
+
Any other path makes the pull request code/operational. That includes source, tests,
|
|
95
|
+
scripts, workflows, configuration, generated baselines, arbitrary root Markdown,
|
|
96
|
+
and a mixed documentation-plus-code change. Those pull requests remain open after
|
|
97
|
+
CI for local maintainer validation and explicit manual merge; automation disables
|
|
98
|
+
auto-merge if it was armed. Documentation remains exempt from product builds and
|
|
99
|
+
tests, but docs-sensitive generated governance and strict OpenSpec consistency are
|
|
100
|
+
checked before integration.
|
|
101
|
+
|
|
102
|
+
After any same-repository pull request into `develop` merges, trusted automation
|
|
103
|
+
reconciles its remote topic branch. Human merges use the close-event workflow;
|
|
104
|
+
documentation merges authored by `GITHUB_TOKEN` use a synchronous fallback because
|
|
105
|
+
GitHub suppresses recursive workflow events. Both delete only an unprotected live
|
|
106
|
+
ref that still equals the pull request's exact merged head SHA. Fork, advanced,
|
|
107
|
+
reserved, protected, malformed, and unmerged refs are preserved and reported.
|
|
108
|
+
|
|
109
|
+
Specification approval and implementation remain separate pull requests. An
|
|
110
|
+
implementation starts from updated `origin/develop` only after its specification
|
|
111
|
+
has merged and implementation was explicitly requested.
|
|
112
|
+
|
|
81
113
|
## Release
|
|
82
114
|
|
|
83
115
|
Two channels, both published by CI from the exact bytes it validated — never from
|
package/bin/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ const { fileURLToPath } = await import("node:url");
|
|
|
5
5
|
const { readFile } = await import("node:fs/promises");
|
|
6
6
|
const { cliCapabilities, dispatchCli } = await import("../dist/cli/index.js");
|
|
7
7
|
|
|
8
|
-
//
|
|
8
|
+
// Compatibility: which commands this build exposes follows from the build's own version, so a
|
|
9
9
|
// released a1 cannot be argued into offering the development profiles.
|
|
10
10
|
const capabilities = cliCapabilities(JSON.parse(await readFile(new URL("package.json", packageRoot), "utf8")).version);
|
|
11
11
|
|
|
@@ -16,7 +16,7 @@ process.exitCode = await dispatchCli(process.argv.slice(2), {
|
|
|
16
16
|
import("../dist/foundation/release/index.js"),
|
|
17
17
|
import("./module-identity.js"),
|
|
18
18
|
]);
|
|
19
|
-
//
|
|
19
|
+
// Compatibility: self-heal the installed tree before the release store copies it: npm 12
|
|
20
20
|
// blocks install scripts by default, so the postinstall that normally
|
|
21
21
|
// repairs module identity may never have run (see bin/module-identity.js).
|
|
22
22
|
const packageRootPath = fileURLToPath(packageRoot);
|
package/bin/sync-pi-tui-proxy.js
CHANGED
|
@@ -35,7 +35,7 @@ const RE_EXPORT_LINE = /^export \* from "[^"]+";$/m;
|
|
|
35
35
|
* pinned Pi resolves. Returns a discriminated outcome; never throws.
|
|
36
36
|
*/
|
|
37
37
|
export function syncPiTuiProxy(packageRoot) {
|
|
38
|
-
// resolvePinnedPiTui answers with a canonical real path, so the directory the
|
|
38
|
+
// Platform: resolvePinnedPiTui answers with a canonical real path, so the directory the
|
|
39
39
|
// relative specifier is computed from must be canonical too (Windows short
|
|
40
40
|
// names, symlinked installs).
|
|
41
41
|
const canonicalRoot = canonical(packageRoot);
|
package/bin/ui.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { assertSinglePiTuiModuleAtLaunch } = await import("./module-identity.js");
|
|
4
4
|
const { fileURLToPath } = await import("node:url");
|
|
5
5
|
|
|
6
|
-
//
|
|
6
|
+
// Compatibility: before the composition loads pinned Pi's terminal stack: confirm A1 and Pi
|
|
7
7
|
// resolve it to the same copy, so extensions and the owned UI share one module
|
|
8
8
|
// identity (see bin/module-identity.js for the full story). Nothing is repaired
|
|
9
9
|
// here — A1's package manifest decides which copy wins, and this only reports
|
|
@@ -12,13 +12,28 @@ assertSinglePiTuiModuleAtLaunch(fileURLToPath(new URL("..", import.meta.url)), m
|
|
|
12
12
|
|
|
13
13
|
const { runSelectedInteractiveRuntime } = await import("../dist/features/launch/index.js");
|
|
14
14
|
|
|
15
|
+
const launchArgs = process.argv.slice(2);
|
|
16
|
+
let sessionPath;
|
|
17
|
+
if (launchArgs.length > 0) {
|
|
18
|
+
if (launchArgs.length !== 2 || launchArgs[0] !== "--session" || launchArgs[1].trim().length === 0) {
|
|
19
|
+
throw new Error("Usage: a1 [--session <session-file>]");
|
|
20
|
+
}
|
|
21
|
+
sessionPath = launchArgs[1];
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
runSelectedInteractiveRuntime(process.env.A1_LAUNCH_PROFILE ?? "a1", {
|
|
16
25
|
ownedUi: async (profileId, ownedSurfaces) => {
|
|
17
|
-
const [{ runOwnedUi }, { composeOwnedUi }] = await Promise.all([
|
|
26
|
+
const [{ createConsoleProjectTrustPrompt, runOwnedUi }, { composeOwnedUi }] = await Promise.all([
|
|
18
27
|
import("../dist/features/owned-ui/index.js"),
|
|
19
28
|
import("../dist/composition/index.js"),
|
|
20
29
|
]);
|
|
21
|
-
const { application, settings } = await composeOwnedUi({
|
|
30
|
+
const { application, settings } = await composeOwnedUi({
|
|
31
|
+
cwd: process.cwd(),
|
|
32
|
+
profileId,
|
|
33
|
+
ownedSurfaces,
|
|
34
|
+
projectTrustPrompt: createConsoleProjectTrustPrompt(),
|
|
35
|
+
...(sessionPath === undefined ? {} : { sessionPath }),
|
|
36
|
+
});
|
|
22
37
|
return await runOwnedUi({ application, ...(settings === null ? {} : { settings }) });
|
|
23
38
|
},
|
|
24
39
|
}).then(
|
package/dist/cli/dispatch.js
CHANGED
|
@@ -85,7 +85,7 @@ export function parseCliCommand(arguments_, capabilities) {
|
|
|
85
85
|
return parseUpdate(rest);
|
|
86
86
|
if (command?.startsWith("update:"))
|
|
87
87
|
return { kind: "noop" };
|
|
88
|
-
//
|
|
88
|
+
// Rationale: unsupported and reserved command spaces are deliberately quiet. Help is
|
|
89
89
|
// explicit, and a typo must never start an interactive or maintenance path.
|
|
90
90
|
return { kind: "noop" };
|
|
91
91
|
}
|
|
@@ -98,7 +98,7 @@ function parseUpdate(rest) {
|
|
|
98
98
|
if (rest.length === 0)
|
|
99
99
|
return { kind: "update", channel: "stable" };
|
|
100
100
|
const [selector, ...values] = rest;
|
|
101
|
-
//
|
|
101
|
+
// Compatibility: removed notation is unsupported rather than deprecated.
|
|
102
102
|
if (selector === "self")
|
|
103
103
|
return { kind: "noop" };
|
|
104
104
|
if (selector === "--develop") {
|
|
@@ -121,7 +121,7 @@ function parseUpdate(rest) {
|
|
|
121
121
|
return pinnedPiUpdateError();
|
|
122
122
|
if (selector !== undefined && selector.startsWith("-"))
|
|
123
123
|
return unknownOption(selector, "update");
|
|
124
|
-
//
|
|
124
|
+
// Rationale: positional update targets are reserved for a future A1-native plugin model.
|
|
125
125
|
return { kind: "noop" };
|
|
126
126
|
}
|
|
127
127
|
function isDevelopmentTarget(target) {
|
package/dist/cli/packages.js
CHANGED
|
@@ -40,7 +40,7 @@ async function runVerb(port, request) {
|
|
|
40
40
|
return request.verb === "install" ? await port.install(request.source) : await port.remove(request.source);
|
|
41
41
|
}
|
|
42
42
|
export function renderPackageOutcome(outcome, profileRoot, style = chalk) {
|
|
43
|
-
//
|
|
43
|
+
// Compatibility: model refresh remains an A1 top-level command. The `a1 pi` compatibility
|
|
44
44
|
// transcript applies to package operations only.
|
|
45
45
|
if (outcome.operation === "refresh-models") {
|
|
46
46
|
if (outcome.status === "failed") {
|
|
@@ -20,7 +20,7 @@ export async function runVersionStats(options) {
|
|
|
20
20
|
output.stderr(`${PRODUCT_TEXT.diagnostic(`could not read its installed version: ${message(error)}`)}\n`);
|
|
21
21
|
return 1;
|
|
22
22
|
}
|
|
23
|
-
//
|
|
23
|
+
// Compatibility: match Pi's release behavior: stable builds print only their installed version.
|
|
24
24
|
// Development builds retain channel visibility for preview comparison and updates.
|
|
25
25
|
if (prerelease(installed) === null) {
|
|
26
26
|
output.stdout(`${installed}\n`);
|
|
@@ -32,7 +32,7 @@ export async function runVersionStats(options) {
|
|
|
32
32
|
output.stderr(`${PRODUCT_TEXT.diagnostic(`could not resolve npm dist-tags: ${remote.error}`)}\n`);
|
|
33
33
|
return 0;
|
|
34
34
|
}
|
|
35
|
-
// npm view respects the user's .npmrc registry and proxy, so it stays primary; the direct
|
|
35
|
+
// Rationale: npm view respects the user's .npmrc registry and proxy, so it stays primary; the direct
|
|
36
36
|
// registry fetch below covers npm CLI versions whose --json output shape we cannot parse.
|
|
37
37
|
async function queryDistTags(runner, fetcher) {
|
|
38
38
|
const fromNpm = await queryDistTagsViaNpm(runner);
|
|
@@ -55,7 +55,7 @@ async function queryDistTagsViaNpm(runner) {
|
|
|
55
55
|
return unavailable(`npm exited with status ${result.code ?? "unknown"}`);
|
|
56
56
|
try {
|
|
57
57
|
const parsed = JSON.parse(result.stdout);
|
|
58
|
-
// npm 12 wraps `npm view <pkg> dist-tags --json` output in a one-element array; older npm returns the bare object.
|
|
58
|
+
// Compatibility: npm 12 wraps `npm view <pkg> dist-tags --json` output in a one-element array; older npm returns the bare object.
|
|
59
59
|
const metadata = Array.isArray(parsed) && parsed.length === 1 ? parsed[0] : parsed;
|
|
60
60
|
return parseDistTags(metadata, "npm");
|
|
61
61
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { type PiEngineAdapter } from "../integrations/pi/engine/index.js";
|
|
1
|
+
import { type PiEngineAdapter, type PiProjectTrustPreflightPrompt } from "../integrations/pi/engine/index.js";
|
|
2
2
|
import { OwnedUiSettingsSession } from "../ui/settings/index.js";
|
|
3
3
|
import type { OwnedUiApplicationPort, PresentationTerminalPort } from "../contracts/presentation/index.js";
|
|
4
4
|
export interface OwnedUiCompositionOptions {
|
|
5
5
|
readonly cwd?: string;
|
|
6
6
|
readonly terminal?: PresentationTerminalPort;
|
|
7
7
|
readonly createPiAdapter?: () => Promise<PiEngineAdapter>;
|
|
8
|
+
/** Exact persisted session selected by the narrow `--session` launch form. */
|
|
9
|
+
readonly sessionPath?: string;
|
|
8
10
|
/**
|
|
9
11
|
* A1 profile whose settings this session reads and writes. Omitted keeps the
|
|
10
12
|
* session settings-free, which is what the pinned comparison paths use.
|
|
@@ -15,6 +17,7 @@ export interface OwnedUiCompositionOptions {
|
|
|
15
17
|
* use the same composition with those surfaces withheld.
|
|
16
18
|
*/
|
|
17
19
|
readonly ownedSurfaces?: "on" | "off";
|
|
20
|
+
readonly projectTrustPrompt?: PiProjectTrustPreflightPrompt;
|
|
18
21
|
}
|
|
19
22
|
export interface OwnedUiComposition {
|
|
20
23
|
readonly application: OwnedUiApplicationPort;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveProductPaths } from "../foundation/lifecycle/index.js";
|
|
2
2
|
import { applyConfiguredPiTheme, getAvailablePiThemes } from "../integrations/pi/components/index.js";
|
|
3
|
-
import { createPiEngineAdapter } from "../integrations/pi/engine/index.js";
|
|
3
|
+
import { createPiEngineAdapter, } from "../integrations/pi/engine/index.js";
|
|
4
4
|
import { OwnedUiSessionShell } from "../integrations/pi/session-ui/index.js";
|
|
5
5
|
import { OwnedUiSettingsSession, OwnedUiSettingsStore } from "../ui/settings/index.js";
|
|
6
6
|
import { createPiTerminalBridge } from "../integrations/pi/tui-runtime/index.js";
|
|
@@ -12,18 +12,21 @@ export async function composeOwnedUi(options = {}) {
|
|
|
12
12
|
const cwd = options.cwd ?? process.cwd();
|
|
13
13
|
const adapter = options.createPiAdapter
|
|
14
14
|
? await options.createPiAdapter()
|
|
15
|
-
: await createPiEngineAdapter({
|
|
15
|
+
: await createPiEngineAdapter({
|
|
16
|
+
cwd,
|
|
17
|
+
availableThemes: () => getAvailablePiThemes().map(theme => theme.name),
|
|
18
|
+
settingsProductMode: options.ownedSurfaces === "off" ? "comparison" : "bare",
|
|
19
|
+
...(options.sessionPath === undefined ? {} : { sessionPath: options.sessionPath }),
|
|
20
|
+
...(options.projectTrustPrompt === undefined ? {} : { projectTrustPrompt: options.projectTrustPrompt }),
|
|
21
|
+
});
|
|
16
22
|
const ownedSurfaces = options.ownedSurfaces !== "off";
|
|
17
23
|
const settings = options.profileId === undefined
|
|
18
24
|
? null
|
|
19
25
|
: new OwnedUiSettingsSession({
|
|
20
26
|
store: new OwnedUiSettingsStore({ configDir: resolveProductPaths().configDir, profileId: options.profileId }),
|
|
21
27
|
agentProvider: () => adapter.settingsPort(),
|
|
22
|
-
...(ownedSurfaces ? {
|
|
23
|
-
hiddenAgentSettingIds: ["tuiMode", "theme", "fullscreenScrollbar", "quietStartup"],
|
|
24
|
-
} : {}),
|
|
25
28
|
});
|
|
26
|
-
//
|
|
29
|
+
// Compatibility: bare A1 intentionally ships one visual target while its UI is being completed:
|
|
27
30
|
// dark, regardless of terminal detection or a previously stored Pi theme. The
|
|
28
31
|
// comparison profile keeps Pi's configured theme behavior and settings surface.
|
|
29
32
|
applyConfiguredPiTheme(ownedSurfaces ? "dark" : adapter.configuredTheme());
|
|
@@ -61,13 +61,15 @@ function pinnedTheme() {
|
|
|
61
61
|
return {
|
|
62
62
|
fg: (token, text) => piTheme().fg(token, text),
|
|
63
63
|
bold: (text) => piTheme().bold(text),
|
|
64
|
-
//
|
|
64
|
+
// Rationale: unpainted, so a list reads against the terminal the reader actually has
|
|
65
65
|
// rather than against the background the configured theme was built for.
|
|
66
66
|
plain: (text) => text,
|
|
67
|
-
//
|
|
67
|
+
// Rationale: quiet, and quieter again: the terminal's own faint attribute over the
|
|
68
68
|
// dimmest colour the theme has.
|
|
69
69
|
disabled: (text) => faint(piTheme().fg("dim", text)),
|
|
70
|
-
|
|
71
|
-
|
|
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
|
+
highlight: (text) => `\u001b[48;2;82;82;82m\u001b[97m${text}\u001b[39m\u001b[49m`,
|
|
73
|
+
panel: (text) => `\u001b[48;2;55;55;55m${text}\u001b[49m`,
|
|
72
74
|
};
|
|
73
75
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentJsonValue, AgentModelDescriptor, AgentResourceDescriptor, AgentSettingDescriptor } from "./domain.js";
|
|
1
|
+
import type { AgentJsonValue, AgentModelDescriptor, AgentSettingChangeOutcome, AgentResourceDescriptor, AgentSettingDescriptor } from "./domain.js";
|
|
2
2
|
export interface AgentModelPort {
|
|
3
3
|
readonly capabilities: {
|
|
4
4
|
readonly selection: boolean;
|
|
@@ -26,7 +26,7 @@ export interface AgentSettingsPort {
|
|
|
26
26
|
};
|
|
27
27
|
listSettings(): Promise<readonly AgentSettingDescriptor[]>;
|
|
28
28
|
readSetting(key: string): Promise<AgentJsonValue | undefined>;
|
|
29
|
-
writeSetting?(key: string, value: AgentJsonValue): Promise<
|
|
29
|
+
writeSetting?(key: string, value: AgentJsonValue): Promise<AgentSettingChangeOutcome>;
|
|
30
30
|
flush?(): Promise<void>;
|
|
31
31
|
}
|
|
32
32
|
export interface AgentResourcesPort {
|
|
@@ -52,10 +52,28 @@ export function assertAgentUsage(value) { for (const amount of [value.inputToken
|
|
|
52
52
|
nonNegative(amount, "usage token count"); if (value.cost !== null && (!(typeof value.cost === "number") || !Number.isFinite(value.cost) || value.cost < 0))
|
|
53
53
|
throw new TypeError("usage cost is invalid"); }
|
|
54
54
|
export function assertAgentModelDescriptor(value) { id(value.providerId, "model provider id"); id(value.modelId, "model id"); text(value.displayName, "model display name"); nonNegative(value.contextWindow, "model context window"); unique(value.thinkingLevels, undefined, "model thinking levels"); }
|
|
55
|
-
export function assertAgentSettingDescriptor(value) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
export function assertAgentSettingDescriptor(value) {
|
|
56
|
+
id(value.key, "setting key");
|
|
57
|
+
if (!["boolean", "number", "string", "enum", "json"].includes(value.valueType)
|
|
58
|
+
|| typeof value.writable !== "boolean"
|
|
59
|
+
|| !["live", "next-session", "next-start", "current-exit"].includes(value.application)
|
|
60
|
+
|| !["agent", "shell", "terminal", "startup", "shutdown", "installation"].includes(value.owner)
|
|
61
|
+
|| typeof value.available !== "boolean"
|
|
62
|
+
|| !(value.limitationReason === null || typeof value.limitationReason === "string")) {
|
|
63
|
+
throw new TypeError("setting descriptor is invalid");
|
|
64
|
+
}
|
|
65
|
+
json(value.storedValue, "stored setting value");
|
|
66
|
+
json(value.effectiveValue, "effective setting value");
|
|
67
|
+
if ((value.available && value.limitationReason !== null)
|
|
68
|
+
|| (!value.available && (value.limitationReason === null || value.limitationReason.length === 0))) {
|
|
69
|
+
throw new TypeError("setting descriptor availability is contradictory");
|
|
70
|
+
}
|
|
71
|
+
if (value.writable && !value.available)
|
|
72
|
+
throw new TypeError("unavailable setting cannot be writable");
|
|
73
|
+
if (value.choices)
|
|
74
|
+
for (const choice of value.choices)
|
|
75
|
+
json(choice, "setting choice");
|
|
76
|
+
}
|
|
59
77
|
export function assertAgentResourceDescriptor(value) { id(value.id, "resource id"); if (!["command", "prompt", "skill", "extension", "other"].includes(value.kind))
|
|
60
78
|
throw new TypeError("resource kind is invalid"); text(value.label, "resource label"); json(value.metadata, "resource metadata"); }
|
|
61
79
|
export function assertAgentThemeDescriptor(value) { id(value.id, "theme id"); text(value.label, "theme label"); if (!value.tokens || typeof value.tokens !== "object" || Object.values(value.tokens).some(token => typeof token !== "string"))
|
|
@@ -48,10 +48,27 @@ export interface AgentModelDescriptor {
|
|
|
48
48
|
readonly contextWindow: number;
|
|
49
49
|
readonly thinkingLevels: readonly string[];
|
|
50
50
|
}
|
|
51
|
+
export type AgentSettingApplicationBoundary = "live" | "next-session" | "next-start" | "current-exit";
|
|
52
|
+
export type AgentSettingOwner = "agent" | "shell" | "terminal" | "startup" | "shutdown" | "installation";
|
|
53
|
+
export interface AgentSettingChangeOutcome {
|
|
54
|
+
readonly status: "applied" | "deferred" | "unavailable" | "failed";
|
|
55
|
+
readonly application: AgentSettingApplicationBoundary;
|
|
56
|
+
readonly storedValue: AgentJsonValue;
|
|
57
|
+
readonly effectiveValue: AgentJsonValue;
|
|
58
|
+
readonly failure: string | null;
|
|
59
|
+
readonly limitationReason: string | null;
|
|
60
|
+
}
|
|
51
61
|
export interface AgentSettingDescriptor {
|
|
52
62
|
readonly key: string;
|
|
53
63
|
readonly valueType: "boolean" | "number" | "string" | "enum" | "json";
|
|
64
|
+
/** A setting is writable only when its declared owner/effect is available. */
|
|
54
65
|
readonly writable: boolean;
|
|
66
|
+
readonly application: AgentSettingApplicationBoundary;
|
|
67
|
+
readonly owner: AgentSettingOwner;
|
|
68
|
+
readonly available: boolean;
|
|
69
|
+
readonly limitationReason: string | null;
|
|
70
|
+
readonly storedValue: AgentJsonValue;
|
|
71
|
+
readonly effectiveValue: AgentJsonValue;
|
|
55
72
|
readonly choices?: readonly AgentJsonValue[];
|
|
56
73
|
/** Label the engine shows for this setting, when it has one. */
|
|
57
74
|
readonly label?: string;
|
|
@@ -91,6 +91,12 @@ export interface OwnedUiOverlay {
|
|
|
91
91
|
readonly modal: boolean;
|
|
92
92
|
readonly payload: unknown;
|
|
93
93
|
}
|
|
94
|
+
export interface OwnedUiTranscriptImageReference {
|
|
95
|
+
readonly assetId: OwnedUiEntityId;
|
|
96
|
+
readonly mimeType: string;
|
|
97
|
+
readonly byteLength: number;
|
|
98
|
+
readonly source: "user" | "tool-result";
|
|
99
|
+
}
|
|
94
100
|
export interface OwnedUiTranscriptBlock {
|
|
95
101
|
readonly id: OwnedUiEntityId;
|
|
96
102
|
readonly kind: OwnedUiTranscriptBlockKind;
|
|
@@ -99,6 +105,8 @@ export interface OwnedUiTranscriptBlock {
|
|
|
99
105
|
readonly title: string | null;
|
|
100
106
|
readonly text: string;
|
|
101
107
|
readonly payload: unknown;
|
|
108
|
+
/** Bounded opaque references; image bytes remain in the session-scoped engine asset store. */
|
|
109
|
+
readonly imageReferences?: readonly OwnedUiTranscriptImageReference[];
|
|
102
110
|
}
|
|
103
111
|
export type OwnedUiSlotId = "theme" | "transcript-block" | "tool-card" | "editor" | "status" | "command" | "selector" | "dialog" | "overlay" | "layout";
|
|
104
112
|
export interface OwnedUiCustomization {
|
|
@@ -14,6 +14,7 @@ const MAX_QUEUE = 32;
|
|
|
14
14
|
const MAX_BADGES = 32;
|
|
15
15
|
const MAX_STATUS_DIAGNOSTICS = 32;
|
|
16
16
|
const MAX_ACTIVE_COMMANDS = 64;
|
|
17
|
+
const IMAGE_REFERENCE_SOURCES = new Set(["user", "tool-result"]);
|
|
17
18
|
const BLOCK_KINDS = new Set([
|
|
18
19
|
"user",
|
|
19
20
|
"assistant",
|
|
@@ -225,6 +226,16 @@ export function assertOwnedUiTranscriptBlock(block) {
|
|
|
225
226
|
assertOptionalText(block.title, "owned-UI transcript block title", MAX_LABEL_LENGTH);
|
|
226
227
|
assertPossiblyEmptyText(block.text, "owned-UI transcript block text", MAX_TEXT_BYTES);
|
|
227
228
|
assertJsonValue(block.payload, "owned-UI transcript block payload", MAX_PAYLOAD_BYTES);
|
|
229
|
+
if (block.imageReferences !== undefined) {
|
|
230
|
+
assertCollection(block.imageReferences, "owned-UI transcript image references", 16);
|
|
231
|
+
for (const reference of block.imageReferences) {
|
|
232
|
+
assertId(reference.assetId, "owned-UI transcript image asset id");
|
|
233
|
+
if (!/^image\/[a-z0-9.+-]+$/i.test(reference.mimeType))
|
|
234
|
+
throw new TypeError("owned-UI transcript image MIME type is invalid");
|
|
235
|
+
assertIntegerInRange(reference.byteLength, 1, 20 * 1024 * 1024, "owned-UI transcript image byte length");
|
|
236
|
+
assertEnum(reference.source, IMAGE_REFERENCE_SOURCES, "owned-UI transcript image source");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
228
239
|
}
|
|
229
240
|
export function assertOwnedUiEditorState(editor) {
|
|
230
241
|
assertPossiblyEmptyText(editor.text, "owned-UI editor text", MAX_TEXT_BYTES);
|
|
@@ -12,7 +12,7 @@ export function resolveDevelopmentLaunchEnvironment(canonicalPackageRoot, releas
|
|
|
12
12
|
const selectedDevelopmentRoot = source[PRODUCT_IDENTITY.environment.developmentRoot];
|
|
13
13
|
const developmentRoot = resolve(selectedDevelopmentRoot
|
|
14
14
|
?? join(checkoutRoot, releaseId, "instances", instanceId));
|
|
15
|
-
//
|
|
15
|
+
// Rationale: runtime/data remain instance-isolated, but product settings are user
|
|
16
16
|
// preferences and must survive a fresh local launch (and a rebuilt candidate).
|
|
17
17
|
// An explicit development root retains its historical self-contained policy.
|
|
18
18
|
const persistentConfigDir = selectedDevelopmentRoot
|
|
@@ -12,6 +12,7 @@ export interface OwnedCommandHandlerContext {
|
|
|
12
12
|
readonly correlationId: string;
|
|
13
13
|
}
|
|
14
14
|
export type OwnedCommandHandler = (context: OwnedCommandHandlerContext) => OwnedUiCommand | Promise<OwnedUiCommand>;
|
|
15
|
+
/** Registers owned UI commands and assigns a monotonic correlation identifier to each dispatch. */
|
|
15
16
|
export declare class OwnedCommandSurface {
|
|
16
17
|
#private;
|
|
17
18
|
register(name: string, handler: OwnedCommandHandler): () => void;
|
|
@@ -22,6 +23,7 @@ export interface ResolvedOwnedUiSlot {
|
|
|
22
23
|
readonly customization: OwnedUiCustomization;
|
|
23
24
|
readonly implementation: OwnedUiSlotImplementation;
|
|
24
25
|
}
|
|
26
|
+
/** Resolves one customization per slot by precedence and version while preserving registration identity. */
|
|
25
27
|
export declare class OwnedUiCustomizationRegistry {
|
|
26
28
|
#private;
|
|
27
29
|
register(customization: OwnedUiCustomization, implementation: OwnedUiSlotImplementation): () => void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assertOwnedUiCustomization, } from "../../contracts/owned-ui/index.js";
|
|
2
|
+
/** Registers owned UI commands and assigns a monotonic correlation identifier to each dispatch. */
|
|
2
3
|
export class OwnedCommandSurface {
|
|
3
4
|
#handlers = new Map();
|
|
4
5
|
#sequence = 0;
|
|
@@ -17,6 +18,7 @@ export class OwnedCommandSurface {
|
|
|
17
18
|
return handler({ sessionId, correlationId: `ui-command-${this.#sequence}` });
|
|
18
19
|
}
|
|
19
20
|
}
|
|
21
|
+
/** Resolves one customization per slot by precedence and version while preserving registration identity. */
|
|
20
22
|
export class OwnedUiCustomizationRegistry {
|
|
21
23
|
#registrations = new Map();
|
|
22
24
|
register(customization, implementation) {
|
|
@@ -17,6 +17,7 @@ export interface OwnedUiDiagnosticsSnapshot {
|
|
|
17
17
|
readonly resources: OwnedUiResourceObservation | null;
|
|
18
18
|
readonly terminalRestorationFailed: boolean;
|
|
19
19
|
}
|
|
20
|
+
/** Retains bounded, redacted UI diagnostics together with frame and resource observations. */
|
|
20
21
|
export declare class OwnedUiDiagnosticsRecorder {
|
|
21
22
|
#private;
|
|
22
23
|
readonly maximumEntries: number;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
export interface OwnedProjectTrustPromptRequest {
|
|
3
|
+
readonly cwd: string;
|
|
4
|
+
readonly defaultDecision: "ask" | "always" | "never";
|
|
5
|
+
}
|
|
6
|
+
export type OwnedProjectTrustPrompt = (request: OwnedProjectTrustPromptRequest) => Promise<boolean | null>;
|
|
7
|
+
interface RawTtyInput extends Readable {
|
|
8
|
+
readonly isTTY?: boolean;
|
|
9
|
+
readonly isRaw?: boolean;
|
|
10
|
+
setRawMode?(enabled: boolean): this;
|
|
11
|
+
}
|
|
12
|
+
export interface ConsoleProjectTrustPromptOptions {
|
|
13
|
+
readonly input?: RawTtyInput;
|
|
14
|
+
readonly output?: Writable & {
|
|
15
|
+
readonly isTTY?: boolean;
|
|
16
|
+
readonly columns?: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Pre-resource startup selector. It uses only fixed product wording and terminal
|
|
21
|
+
* controls: no project setting, theme, extension, prompt, package, or skill is
|
|
22
|
+
* consulted before the trust result exists.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createConsoleProjectTrustPrompt(options?: ConsoleProjectTrustPromptOptions): OwnedProjectTrustPrompt;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const ENTER_ALTERNATE_SCREEN = "\u001b[?1049h";
|
|
2
|
+
const LEAVE_ALTERNATE_SCREEN = "\u001b[?1049l";
|
|
3
|
+
const HIDE_CURSOR = "\u001b[?25l";
|
|
4
|
+
const SHOW_CURSOR = "\u001b[?25h";
|
|
5
|
+
const CLEAR_HOME = "\u001b[2J\u001b[H";
|
|
6
|
+
const ACCENT = "\u001b[38;2;138;190;183m";
|
|
7
|
+
const MUTED = "\u001b[38;2;128;128;128m";
|
|
8
|
+
const DIM = "\u001b[38;2;102;102;102m";
|
|
9
|
+
const RESET_FG = "\u001b[39m";
|
|
10
|
+
const BOLD = "\u001b[1m";
|
|
11
|
+
const RESET_BOLD = "\u001b[22m";
|
|
12
|
+
/**
|
|
13
|
+
* Pre-resource startup selector. It uses only fixed product wording and terminal
|
|
14
|
+
* controls: no project setting, theme, extension, prompt, package, or skill is
|
|
15
|
+
* consulted before the trust result exists.
|
|
16
|
+
*/
|
|
17
|
+
export function createConsoleProjectTrustPrompt(options = {}) {
|
|
18
|
+
const input = options.input ?? process.stdin;
|
|
19
|
+
const output = options.output ?? process.stdout;
|
|
20
|
+
return async ({ cwd }) => {
|
|
21
|
+
if (input.isTTY !== true || output.isTTY !== true) {
|
|
22
|
+
throw new Error("an interactive terminal is unavailable");
|
|
23
|
+
}
|
|
24
|
+
const wasRaw = input.isRaw === true;
|
|
25
|
+
let selected = 0;
|
|
26
|
+
let restored = false;
|
|
27
|
+
const restore = () => {
|
|
28
|
+
if (restored)
|
|
29
|
+
return;
|
|
30
|
+
restored = true;
|
|
31
|
+
input.setRawMode?.(wasRaw);
|
|
32
|
+
output.write(`${CLEAR_HOME}${SHOW_CURSOR}${LEAVE_ALTERNATE_SCREEN}`);
|
|
33
|
+
};
|
|
34
|
+
const render = () => {
|
|
35
|
+
const width = Math.max(20, output.columns ?? 80);
|
|
36
|
+
const lines = [
|
|
37
|
+
`${BOLD}${ACCENT}Trust project folder?${RESET_FG}${RESET_BOLD}`,
|
|
38
|
+
cwd,
|
|
39
|
+
"",
|
|
40
|
+
"This allows A1 to load project settings and resources, install missing project packages, and execute project extensions.",
|
|
41
|
+
"",
|
|
42
|
+
optionRow("Trust", selected === 0),
|
|
43
|
+
optionRow("Do not trust", selected === 1),
|
|
44
|
+
"",
|
|
45
|
+
`${DIM} ↑/↓ to navigate · Enter to select · Esc to cancel${RESET_FG}`,
|
|
46
|
+
];
|
|
47
|
+
output.write(`${CLEAR_HOME}${lines.map(line => clipAnsiSafe(line, width)).join("\n")}`);
|
|
48
|
+
};
|
|
49
|
+
try {
|
|
50
|
+
output.write(`${ENTER_ALTERNATE_SCREEN}${HIDE_CURSOR}`);
|
|
51
|
+
input.setRawMode?.(true);
|
|
52
|
+
render();
|
|
53
|
+
return await new Promise((resolve, reject) => {
|
|
54
|
+
let settled = false;
|
|
55
|
+
const finish = (value) => {
|
|
56
|
+
if (settled)
|
|
57
|
+
return;
|
|
58
|
+
settled = true;
|
|
59
|
+
cleanup();
|
|
60
|
+
restore();
|
|
61
|
+
resolve(value);
|
|
62
|
+
};
|
|
63
|
+
const fail = (error) => {
|
|
64
|
+
if (settled)
|
|
65
|
+
return;
|
|
66
|
+
settled = true;
|
|
67
|
+
cleanup();
|
|
68
|
+
restore();
|
|
69
|
+
reject(error);
|
|
70
|
+
};
|
|
71
|
+
const onData = (chunk) => {
|
|
72
|
+
const data = chunk.toString();
|
|
73
|
+
for (let index = 0; index < data.length;) {
|
|
74
|
+
if (data.startsWith("\u001b[A", index)) {
|
|
75
|
+
selected = 0;
|
|
76
|
+
index += 3;
|
|
77
|
+
render();
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (data.startsWith("\u001b[B", index) || data[index] === "\t") {
|
|
81
|
+
selected = selected === 0 ? 1 : 0;
|
|
82
|
+
index += data[index] === "\t" ? 1 : 3;
|
|
83
|
+
render();
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const key = data[index] ?? "";
|
|
87
|
+
index += 1;
|
|
88
|
+
if (key === "\r" || key === "\n") {
|
|
89
|
+
finish(selected === 0);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (key === "\u001b" || key === "\u0003") {
|
|
93
|
+
finish(null);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// Compatibility: retain y/n aliases for terminals or automation that cannot send
|
|
97
|
+
// navigation keys; the visible interaction remains selector-first.
|
|
98
|
+
if (key === "y" || key === "Y") {
|
|
99
|
+
finish(true);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (key === "n" || key === "N") {
|
|
103
|
+
finish(false);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
const onEnd = () => finish(null);
|
|
109
|
+
const onError = (error) => fail(error);
|
|
110
|
+
const cleanup = () => {
|
|
111
|
+
input.off("data", onData);
|
|
112
|
+
input.off("end", onEnd);
|
|
113
|
+
input.off("error", onError);
|
|
114
|
+
};
|
|
115
|
+
input.on("data", onData);
|
|
116
|
+
input.once("end", onEnd);
|
|
117
|
+
input.once("error", onError);
|
|
118
|
+
input.resume();
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
restore();
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function optionRow(label, selected) {
|
|
127
|
+
return selected
|
|
128
|
+
? `${ACCENT}→ ${label}${RESET_FG}`
|
|
129
|
+
: ` ${label}`;
|
|
130
|
+
}
|
|
131
|
+
/** Avoid replaying or manufacturing control payloads while keeping fixed SGR. */
|
|
132
|
+
function clipAnsiSafe(line, width) {
|
|
133
|
+
let visible = 0;
|
|
134
|
+
let output = "";
|
|
135
|
+
for (let index = 0; index < line.length && visible < width;) {
|
|
136
|
+
if (line[index] === "\u001b") {
|
|
137
|
+
const match = line.slice(index).match(/^\u001b\[[0-9;:]*m/);
|
|
138
|
+
if (match !== null) {
|
|
139
|
+
output += match[0];
|
|
140
|
+
index += match[0].length;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
output += line[index];
|
|
145
|
+
index += 1;
|
|
146
|
+
visible += 1;
|
|
147
|
+
}
|
|
148
|
+
return `${output}${RESET_FG}${RESET_BOLD}`;
|
|
149
|
+
}
|