@timurproko/a1 0.2.1-dev.606 → 0.2.1
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 -120
- package/bin/ui.js +5 -2
- package/dist/app/session-shell/prompt-chips.d.ts +3 -9
- package/dist/app/session-shell/prompt-chips.js +6 -14
- package/dist/app/session-shell/session-shell-root.js +7 -3
- package/dist/app/session-shell/session-shell.js +14 -1
- package/dist/features/owned-ui/project-trust-dialog.d.ts +1 -1
- package/dist/features/owned-ui/project-trust-dialog.js +9 -5
- package/dist/features/owned-ui/project-trust-prompt.d.ts +6 -1
- package/dist/features/owned-ui/project-trust-prompt.js +35 -15
- package/dist/foundation/terminal-cleanup/terminal-reset.js +2 -1
- package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +6 -4
- package/dist/integrations/pi/components/upstream/history/editor-core.js +33 -13
- package/dist/integrations/pi/engine/project-trust-preflight.d.ts +6 -1
- package/dist/integrations/pi/engine/project-trust-preflight.js +41 -3
- package/dist/integrations/pi/engine/runtime-integration.js +1 -1
- package/dist/integrations/pi/engine/session-runtime.js +4 -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/docs/architecture/history-editor-provenance.md +13 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,31 +3,10 @@
|
|
|
3
3
|
## Install
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
# release
|
|
7
|
-
npx -y @timurproko/a1-install
|
|
8
|
-
|
|
9
|
-
#
|
|
10
|
-
npx -y @timurproko/a1-install --develop
|
|
11
|
-
|
|
12
|
-
# preview number
|
|
13
|
-
npx -y @timurproko/a1-install --develop 107
|
|
14
|
-
|
|
15
|
-
# exact preview
|
|
16
|
-
npx -y @timurproko/a1-install --develop 0.1.8-dev.107
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
The installer keeps npm's dependency transcript out of the normal terminal output,
|
|
20
|
-
shows one progress row, activates the installed release, and verifies the command
|
|
21
|
-
before reporting success. The shorter `npx @timurproko/a1-install` form also works,
|
|
22
|
-
but npm may ask for first-use confirmation; `-y` avoids that prompt.
|
|
23
|
-
|
|
24
|
-
Direct npm installation remains available as a fallback or manual recovery path.
|
|
25
|
-
npm's `latest` tag carries the release and `next` carries develop:
|
|
26
|
-
|
|
27
|
-
```sh
|
|
28
|
-
npm install -g @timurproko/a1@latest # release
|
|
29
|
-
npm install -g @timurproko/a1@next # develop
|
|
30
|
-
npm install -g @timurproko/a1@0.1.8-dev.107 # exact preview
|
|
6
|
+
npx -y @timurproko/a1-install # release
|
|
7
|
+
npx -y @timurproko/a1-install --develop # develop
|
|
8
|
+
npx -y @timurproko/a1-install --develop 107 # preview number
|
|
9
|
+
npx -y @timurproko/a1-install --develop 0.1.8-dev.107 # exact preview
|
|
31
10
|
```
|
|
32
11
|
|
|
33
12
|
## Use
|
|
@@ -36,126 +15,59 @@ npm install -g @timurproko/a1@0.1.8-dev.107 # exact preview
|
|
|
36
15
|
a1 # launch A1 (profile: ~/.a1/agent)
|
|
37
16
|
a1 help # show all commands
|
|
38
17
|
a1 version # show the version
|
|
18
|
+
a1 pi # vanilla Pi oracle: ~/.pi/agent
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Update:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
39
24
|
a1 update # install the release
|
|
25
|
+
a1 update --models # refresh A1's model catalogs
|
|
40
26
|
a1 update --develop # install develop
|
|
41
27
|
a1 update --develop 107 # install preview 107
|
|
42
28
|
a1 update --develop 0.1.8-dev.107 # install that exact preview
|
|
43
|
-
a1 update --models # refresh A1's model catalogs
|
|
44
|
-
a1 update --extensions # update every installed package
|
|
45
|
-
a1 update npm:pi-mcp-adapter # update one package
|
|
46
29
|
```
|
|
47
30
|
|
|
48
|
-
|
|
49
|
-
current develop and release channel versions.
|
|
50
|
-
|
|
51
|
-
Develop previews add the Pi comparison profile; release builds do not carry it.
|
|
31
|
+
## Extensions
|
|
52
32
|
|
|
53
33
|
```sh
|
|
54
|
-
a1 pi
|
|
34
|
+
a1 install npm:pi-mcp-adapter # install a package
|
|
35
|
+
a1 remove npm:pi-mcp-adapter # remove it (alias: a1 uninstall)
|
|
36
|
+
a1 list # list installed packages
|
|
55
37
|
```
|
|
56
38
|
|
|
57
|
-
|
|
58
|
-
`update:<preview>` forms are not aliases; use `update --develop`.
|
|
59
|
-
|
|
60
|
-
## Extensions
|
|
61
|
-
|
|
62
|
-
Pi-compatible extension packages install into A1's own profile (`~/.a1/agent`), so
|
|
63
|
-
bare `a1` loads them and the `a1 pi` comparison profile does not. Sources use Pi's
|
|
64
|
-
`npm:`, git, or local-path grammar.
|
|
39
|
+
Update:
|
|
65
40
|
|
|
66
41
|
```sh
|
|
67
|
-
a1
|
|
68
|
-
a1
|
|
69
|
-
a1 list # list installed packages
|
|
70
|
-
a1 update --extensions # update every installed package
|
|
71
|
-
a1 update npm:pi-mcp-adapter # update one
|
|
72
|
-
a1 update --models # refresh A1's model catalogs
|
|
42
|
+
a1 update --extensions # update every installed package
|
|
43
|
+
a1 update npm:pi-mcp-adapter # update one
|
|
73
44
|
```
|
|
74
45
|
|
|
75
|
-
A1 pins the Pi runtime carried by each release, so Pi self-update forms are refused.
|
|
76
|
-
Update A1 itself with `a1 update` or `a1 update --develop`.
|
|
77
|
-
|
|
78
|
-
A running session picks up a newly installed package after a restart.
|
|
79
|
-
Configuration is isolated the same way: bare `a1` reads `~/.a1/agent` (or the
|
|
80
|
-
project), never `~/.pi/agent`. Configure MCP with `/mcp setup` inside bare `a1`.
|
|
81
|
-
|
|
82
46
|
## Develop
|
|
83
47
|
|
|
84
|
-
A source build needs Node.js `>=22.19.0 <25`, npm 11, git, and Rust/Cargo 1.85 or
|
|
85
|
-
newer—the build compiles the `native/process-guardian` crate. Publishing a release
|
|
86
|
-
or a pull request additionally needs an authenticated [GitHub CLI](https://cli.github.com).
|
|
87
|
-
`npm run doctor` reports every prerequisite with the exact command that installs
|
|
88
|
-
whatever is missing, and `npm run build` runs the same check first so a fresh clone
|
|
89
|
-
fails by name instead of inside a resolver or Cargo.
|
|
90
|
-
|
|
91
48
|
```sh
|
|
92
|
-
npm run doctor
|
|
93
|
-
npm ci
|
|
94
|
-
npm run build
|
|
95
|
-
npm start
|
|
96
|
-
npm run start:pi
|
|
97
|
-
npm run test:fast
|
|
98
|
-
npm run test:full
|
|
49
|
+
npm run doctor # report Node, npm, git, Rust, and dependency readiness
|
|
50
|
+
npm ci # install exact locked dependencies
|
|
51
|
+
npm run build # compile TypeScript and the process guardian into dist
|
|
52
|
+
npm start # build and launch a source `a1`
|
|
53
|
+
npm run start:pi # build and launch a source `a1 pi`
|
|
54
|
+
npm run test:fast # typecheck + fast suite (alias: npm test)
|
|
55
|
+
npm run test:full # complete non-physical suite
|
|
99
56
|
```
|
|
100
57
|
|
|
101
|
-
|
|
102
|
-
`{working-dir}` is the session's initial working directory. For working directory
|
|
103
|
-
`D:/Git/a1`, use `D:/Git/a1/.worktrees/<task-id>`—never a sibling such as
|
|
104
|
-
`D:/Git/a1-<task-id>`. The primary worktree remains on `develop` for integration only.
|
|
105
|
-
|
|
106
|
-
## Release
|
|
107
|
-
|
|
108
|
-
Two channels, both published by CI from the exact bytes it validated — never from
|
|
109
|
-
a workstation.
|
|
58
|
+
## Publish
|
|
110
59
|
|
|
111
|
-
### Develop
|
|
112
|
-
|
|
113
|
-
A preview is `<major.minor.patch>-dev.<pull-request number>`, e.g. `0.1.8-dev.107`.
|
|
114
|
-
The nightly run (`03:17 UTC`) verifies current `origin/develop` and publishes only
|
|
115
|
-
when that source's preview is absent. `npm run develop` requests the same GitHub
|
|
116
|
-
Actions run and waits for it. The published version list is authoritative: an
|
|
117
|
-
unpublished number is refused.
|
|
118
|
-
|
|
119
|
-
Publish:
|
|
120
|
-
|
|
121
|
-
```sh
|
|
122
|
-
npm run develop # request the preview publish run and wait for it
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
Install:
|
|
60
|
+
### Develop
|
|
126
61
|
|
|
127
62
|
```sh
|
|
128
|
-
|
|
129
|
-
a1 update --develop 0.1.8-dev.107 # install that exact full preview version
|
|
63
|
+
npm run develop # request the preview publish
|
|
130
64
|
```
|
|
131
65
|
|
|
132
66
|
### Release
|
|
133
67
|
|
|
134
68
|
```sh
|
|
135
|
-
npm run release -- patch
|
|
136
|
-
npm run release -- minor
|
|
137
|
-
npm run release -- major
|
|
138
|
-
npm run release -- 0.4.0
|
|
69
|
+
npm run release -- patch # 0.1.8-dev -> 0.1.8
|
|
70
|
+
npm run release -- minor # 0.1.8-dev -> 0.2.0
|
|
71
|
+
npm run release -- major # 0.1.8-dev -> 1.0.0
|
|
72
|
+
npm run release -- 0.4.0 # an exact stable version
|
|
139
73
|
```
|
|
140
|
-
|
|
141
|
-
Run from the repository root on a clean `develop` matching `origin/develop` that
|
|
142
|
-
declares the open `-dev` version. A target is required; bare `npm run release`
|
|
143
|
-
displays usage and releases nothing. `patch` promotes the current prerelease rather
|
|
144
|
-
than skipping its stable version.
|
|
145
|
-
|
|
146
|
-
The stable version is never committed. The command dispatches publication for the
|
|
147
|
-
exact authoritative `develop` commit with the stable version named in the request,
|
|
148
|
-
and waits for success. CI stamps that version on the checked-out source before
|
|
149
|
-
packing, validates the packed release on Windows, Linux, and macOS, publishes to
|
|
150
|
-
npm `latest` with provenance, then writes the `v<version>` tag on that same
|
|
151
|
-
`develop` commit, records the GitHub Release, and fast-forwards `master`.
|
|
152
|
-
|
|
153
|
-
Only after confirmed publication of `0.1.8` does the command prepare the one
|
|
154
|
-
version-only PR, `0.1.9-dev`, in an isolated detached worktree, print its URL, and
|
|
155
|
-
wait for you to **merge it manually**. It is not auto-merged, and green CI alone does
|
|
156
|
-
not advance the release. Work added to your checkout during the wait is preserved,
|
|
157
|
-
not reset. If publication fails or is uncertain, no reopening PR is prepared. If
|
|
158
|
-
publication succeeded but reopening failed, merge or repair the reported PR by
|
|
159
|
-
hand; do not republish the immutable stable version.
|
|
160
|
-
|
|
161
|
-
`docs/ci-release-runbook.md` has the full picture.
|
package/bin/ui.js
CHANGED
|
@@ -67,9 +67,12 @@ Promise.resolve().then(() => {
|
|
|
67
67
|
return terminateOwnedUiProcess(code);
|
|
68
68
|
},
|
|
69
69
|
error => {
|
|
70
|
-
|
|
70
|
+
const sessionSelectionError = error?.name === "PiSessionSelectionError";
|
|
71
|
+
const trustExit = error?.name === "ProjectTrustPromptExitError";
|
|
72
|
+
if (fatal && !sessionSelectionError && !trustExit) { fatal.fail(error); return; }
|
|
71
73
|
fatal?.remove();
|
|
74
|
+
if (trustExit) return terminateOwnedUiProcess(error.exitCode);
|
|
72
75
|
console.error(error instanceof Error ? error.message : String(error));
|
|
73
|
-
process.exitCode =
|
|
76
|
+
process.exitCode = sessionSelectionError ? error.exitCode : 1;
|
|
74
77
|
},
|
|
75
78
|
);
|
|
@@ -61,15 +61,9 @@ export declare class PromptChipStore {
|
|
|
61
61
|
readonly image: PromptImageAttachment;
|
|
62
62
|
}[];
|
|
63
63
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* uses. Image chips whose sidecar returns null are silently stripped without a placeholder.
|
|
68
|
-
*
|
|
69
|
-
* Every chip returned is registered in the session `#chips` map so atomic-range,
|
|
70
|
-
* hyperlink-range, and submission-expansion paths behave identically to a freshly typed draft.
|
|
71
|
-
* Callers should treat the returned string as programmatic editor text, not another draft to
|
|
72
|
-
* scan.
|
|
64
|
+
* Builds editor-ready recall. Images use sidecars, URL/path text is re-registered,
|
|
65
|
+
* multiline text stays expanded, and missing image payloads silently remove their tags.
|
|
66
|
+
* Returned chips remain live for atomic editing, links, and submission expansion.
|
|
73
67
|
*/
|
|
74
68
|
rehydrateHistoryText(text: string, resolveImage: (id: string) => PromptImageAttachment | null): string;
|
|
75
69
|
prepareSubmission(text: string): PreparedPrompt;
|
|
@@ -320,15 +320,9 @@ export class PromptChipStore {
|
|
|
320
320
|
return results;
|
|
321
321
|
}
|
|
322
322
|
/**
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
* uses. Image chips whose sidecar returns null are silently stripped without a placeholder.
|
|
327
|
-
*
|
|
328
|
-
* Every chip returned is registered in the session `#chips` map so atomic-range,
|
|
329
|
-
* hyperlink-range, and submission-expansion paths behave identically to a freshly typed draft.
|
|
330
|
-
* Callers should treat the returned string as programmatic editor text, not another draft to
|
|
331
|
-
* scan.
|
|
323
|
+
* Builds editor-ready recall. Images use sidecars, URL/path text is re-registered,
|
|
324
|
+
* multiline text stays expanded, and missing image payloads silently remove their tags.
|
|
325
|
+
* Returned chips remain live for atomic editing, links, and submission expansion.
|
|
332
326
|
*/
|
|
333
327
|
rehydrateHistoryText(text, resolveImage) {
|
|
334
328
|
if (!text)
|
|
@@ -346,6 +340,7 @@ export class PromptChipStore {
|
|
|
346
340
|
this.#chips.set(match.text, { kind: "image", tag: match.text, image: attachment });
|
|
347
341
|
return match.text;
|
|
348
342
|
});
|
|
343
|
+
const preserveMultiline = resolvedImages.includes("\n");
|
|
349
344
|
// Rationale: split around any surviving image tags so paste-time classification runs on the
|
|
350
345
|
// intervening prose exactly as it would on a fresh clipboard payload, plus a substring URL
|
|
351
346
|
// scan so embedded links become atomic chips even in the middle of surrounding text.
|
|
@@ -369,12 +364,9 @@ export class PromptChipStore {
|
|
|
369
364
|
const body = segment.slice(leading.length, segment.length - trailing.length);
|
|
370
365
|
if (body.length === 0)
|
|
371
366
|
return segment;
|
|
372
|
-
// Rationale:
|
|
373
|
-
// rules, producing a URL chip whenever a bare http(s) URL appears in the prose. Whole-text
|
|
374
|
-
// classification runs afterwards to catch path / large-paste candidates that consume the
|
|
375
|
-
// entire remaining body.
|
|
367
|
+
// Rationale: keep multiline history readable; otherwise reuse paste-time URL/path classification.
|
|
376
368
|
const withUrlChips = this.#rehydrateUrlSubstrings(body);
|
|
377
|
-
if (withUrlChips !== body)
|
|
369
|
+
if (withUrlChips !== body || preserveMultiline)
|
|
378
370
|
return `${leading}${withUrlChips}${trailing}`;
|
|
379
371
|
const classified = this.transformPastedContent({ kind: "text", text: body });
|
|
380
372
|
return `${leading}${classified}${trailing}`;
|
|
@@ -678,7 +678,10 @@ export class OwnedUiSessionShellRoot {
|
|
|
678
678
|
return cached;
|
|
679
679
|
const diagnostics = this.#view.diagnostics;
|
|
680
680
|
const startupRows = diagnostics
|
|
681
|
-
|
|
681
|
+
// Compatibility: the pinned route keeps trust failures with Pi-style startup diagnostics;
|
|
682
|
+
// bare A1 presents them in its prompt-adjacent notice dock instead.
|
|
683
|
+
.filter(diagnostic => diagnostic.code === "engine-startup"
|
|
684
|
+
|| !this.#customViewport && diagnostic.code === "project-trust")
|
|
682
685
|
.flatMap(diagnostic => renderPiShellStartupDiagnostic(diagnostic, width));
|
|
683
686
|
const resourceRows = this.#customViewport ? [] : [...this.resources.render(width)];
|
|
684
687
|
if (resourceRows.at(-1) === "")
|
|
@@ -744,8 +747,9 @@ export class OwnedUiSessionShellRoot {
|
|
|
744
747
|
? createPiShellCollapsedChangelog().render(width)
|
|
745
748
|
: createPiShellChangelog(diagnostic.message).render(width)));
|
|
746
749
|
rows.push(...diagnostics
|
|
747
|
-
.filter(diagnostic => diagnostic.code !== "engine-startup" && diagnostic.code !== "
|
|
748
|
-
&& diagnostic.code !== "
|
|
750
|
+
.filter(diagnostic => diagnostic.code !== "engine-startup" && diagnostic.code !== "project-trust"
|
|
751
|
+
&& diagnostic.code !== "package-updates" && diagnostic.code !== "changelog-collapsed"
|
|
752
|
+
&& diagnostic.code !== "changelog-expanded")
|
|
749
753
|
.slice(-3)
|
|
750
754
|
.flatMap(diagnostic => renderPiShellTranscriptBlock({
|
|
751
755
|
id: `diagnostic-${diagnostic.sequence}`,
|
|
@@ -55,8 +55,9 @@ export class OwnedUiSessionShell {
|
|
|
55
55
|
#shutdownPromise;
|
|
56
56
|
#disposePromise;
|
|
57
57
|
#pointerReporting = false;
|
|
58
|
-
// Invariant:
|
|
58
|
+
// Invariant: translated startup diagnostics create at most one dock notice per kind per shell.
|
|
59
59
|
#startupChangelogHandled = false;
|
|
60
|
+
#startupTrustHandled = false;
|
|
60
61
|
#customViewport;
|
|
61
62
|
#responseCopy;
|
|
62
63
|
// Rationale: only an executor this shell created owns a spare copy helper worth warming and disposing.
|
|
@@ -1706,6 +1707,7 @@ export class OwnedUiSessionShell {
|
|
|
1706
1707
|
this.root.update(view);
|
|
1707
1708
|
this.#syncDialog(view.dialog);
|
|
1708
1709
|
this.#presentStartupChangelog(view);
|
|
1710
|
+
this.#presentStartupTrustWarning(view);
|
|
1709
1711
|
this.runtime.requestRender();
|
|
1710
1712
|
for (const listener of this.#listeners)
|
|
1711
1713
|
listener(view);
|
|
@@ -1723,6 +1725,17 @@ export class OwnedUiSessionShell {
|
|
|
1723
1725
|
this.#startupChangelogHandled = true;
|
|
1724
1726
|
this.root.appendWorkflowStatus(STARTUP_CHANGELOG_NOTICE);
|
|
1725
1727
|
}
|
|
1728
|
+
// Rationale: a cancelled or failed trust choice explains the restricted session where the
|
|
1729
|
+
// user will next type, rather than leaving a lone warning at the top of an empty viewport.
|
|
1730
|
+
#presentStartupTrustWarning(view) {
|
|
1731
|
+
if (this.#startupTrustHandled || !this.#customViewport)
|
|
1732
|
+
return;
|
|
1733
|
+
const diagnostic = view.diagnostics.find(candidate => candidate.code === "project-trust");
|
|
1734
|
+
if (diagnostic === undefined || !this.runtime.active)
|
|
1735
|
+
return;
|
|
1736
|
+
this.#startupTrustHandled = true;
|
|
1737
|
+
this.root.appendWorkflowMessage({ kind: "warning", message: diagnostic.message });
|
|
1738
|
+
}
|
|
1726
1739
|
#openOwnedRoute(route, input) {
|
|
1727
1740
|
const surface = this.#routeHost?.open(route, input) ?? null;
|
|
1728
1741
|
if (surface === null)
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Fixed startup-safe rendering only; project-derived presentation is forbidden before trust resolves. */
|
|
2
|
-
export declare function renderProjectTrustDialog(cwd: string, selected: number, terminalWidth: number, terminalRows: number): readonly string[];
|
|
2
|
+
export declare function renderProjectTrustDialog(cwd: string, options: readonly string[], selected: number, terminalWidth: number, terminalRows: number): readonly string[];
|
|
@@ -7,13 +7,13 @@ const BOLD = "\u001b[1m";
|
|
|
7
7
|
const RESET_BOLD = "\u001b[22m";
|
|
8
8
|
const EXPLANATION = "This allows to load project settings and resources, install missing project packages, and execute project extensions.";
|
|
9
9
|
/** Fixed startup-safe rendering only; project-derived presentation is forbidden before trust resolves. */
|
|
10
|
-
export function renderProjectTrustDialog(cwd, selected, terminalWidth, terminalRows) {
|
|
10
|
+
export function renderProjectTrustDialog(cwd, options, selected, terminalWidth, terminalRows) {
|
|
11
11
|
const width = Math.max(1, terminalWidth);
|
|
12
12
|
const rule = `${BORDER}${"─".repeat(width)}${RESET_FG}`;
|
|
13
13
|
const title = ` ${BOLD}${ACCENT}Trust project folder?${RESET_FG}${RESET_BOLD}`;
|
|
14
14
|
const path = ` ${MUTED}${sanitize(cwd)}${RESET_FG}`;
|
|
15
|
-
const choices =
|
|
16
|
-
const hint = ` ${DIM}↑/↓${MUTED} to navigate ${DIM}Enter${MUTED} to select ${DIM}Esc${MUTED} to
|
|
15
|
+
const choices = options.map((label, index) => choice(label, selected === index));
|
|
16
|
+
const hint = ` ${DIM}↑/↓${MUTED} to navigate ${DIM}Enter${MUTED} to select ${DIM}Esc${MUTED} to exit${RESET_FG}`;
|
|
17
17
|
const explanation = wrap(EXPLANATION, Math.max(1, width - 2)).map(line => ` ${line}`);
|
|
18
18
|
const preferred = [rule, title, path, "", ...explanation, "", ...choices, "", hint, rule];
|
|
19
19
|
if (preferred.length <= terminalRows)
|
|
@@ -28,14 +28,18 @@ export function renderProjectTrustDialog(cwd, selected, terminalWidth, terminalR
|
|
|
28
28
|
if (essential.length <= terminalRows)
|
|
29
29
|
return essential;
|
|
30
30
|
if (terminalRows >= 3)
|
|
31
|
-
return [...choices, hint];
|
|
31
|
+
return [title, ...choiceWindow(choices, selected, terminalRows - 2), hint];
|
|
32
32
|
if (terminalRows === 2)
|
|
33
|
-
return choices;
|
|
33
|
+
return choiceWindow(choices, selected, 2);
|
|
34
34
|
return [choices[selected] ?? choices[0] ?? ""];
|
|
35
35
|
}
|
|
36
36
|
function choice(label, selected) {
|
|
37
37
|
return selected ? ` ${ACCENT}→ ${label}${RESET_FG}` : ` ${label}`;
|
|
38
38
|
}
|
|
39
|
+
function choiceWindow(choices, selected, capacity) {
|
|
40
|
+
const start = Math.min(Math.max(0, selected - Math.floor(capacity / 2)), Math.max(0, choices.length - capacity));
|
|
41
|
+
return choices.slice(start, start + capacity);
|
|
42
|
+
}
|
|
39
43
|
function wrap(text, width) {
|
|
40
44
|
const lines = [];
|
|
41
45
|
let line = "";
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
export type OwnedProjectTrustChoiceId = "trust" | "trust-parent" | "trust-session" | "deny" | "deny-session";
|
|
2
3
|
export interface OwnedProjectTrustPromptRequest {
|
|
3
4
|
readonly cwd: string;
|
|
4
5
|
readonly defaultDecision: "ask" | "always" | "never";
|
|
6
|
+
readonly choices: readonly {
|
|
7
|
+
readonly id: OwnedProjectTrustChoiceId;
|
|
8
|
+
readonly label: string;
|
|
9
|
+
}[];
|
|
5
10
|
}
|
|
6
|
-
export type OwnedProjectTrustPrompt = (request: OwnedProjectTrustPromptRequest) => Promise<
|
|
11
|
+
export type OwnedProjectTrustPrompt = (request: OwnedProjectTrustPromptRequest) => Promise<OwnedProjectTrustChoiceId | null>;
|
|
7
12
|
interface RawTtyInput extends Readable {
|
|
8
13
|
readonly isTTY?: boolean;
|
|
9
14
|
readonly isRaw?: boolean;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
import { EMERGENCY_TERMINAL_RESET } from "../../foundation/terminal-cleanup/terminal-reset.js";
|
|
1
2
|
const ENTER_ALTERNATE_SCREEN = "\u001b[?1049h";
|
|
2
|
-
const LEAVE_ALTERNATE_SCREEN = "\u001b[?1049l";
|
|
3
3
|
const HIDE_CURSOR = "\u001b[?25l";
|
|
4
|
-
const SHOW_CURSOR = "\u001b[?25h";
|
|
5
4
|
const CLEAR_HOME = "\u001b[2J\u001b[H";
|
|
6
5
|
const ACCENT = "\u001b[38;2;138;190;183m";
|
|
7
6
|
const MUTED = "\u001b[38;2;128;128;128m";
|
|
@@ -9,15 +8,26 @@ const DIM = "\u001b[38;2;102;102;102m";
|
|
|
9
8
|
const RESET_FG = "\u001b[39m";
|
|
10
9
|
const BOLD = "\u001b[1m";
|
|
11
10
|
const RESET_BOLD = "\u001b[22m";
|
|
11
|
+
const PROJECT_TRUST_EXIT = "ProjectTrustPromptExitError";
|
|
12
|
+
class ProjectTrustPromptExitError extends Error {
|
|
13
|
+
exitCode = 0;
|
|
14
|
+
constructor() {
|
|
15
|
+
super("Project trust prompt exited");
|
|
16
|
+
this.name = PROJECT_TRUST_EXIT;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
12
19
|
/** Fixed pre-resource selector; no project resource is available. */
|
|
13
20
|
export function createConsoleProjectTrustPrompt(options = {}) {
|
|
14
21
|
const input = options.input ?? process.stdin;
|
|
15
22
|
const output = options.output ?? process.stdout;
|
|
16
|
-
return async ({ cwd }) => {
|
|
23
|
+
return async ({ cwd, choices }) => {
|
|
17
24
|
if (input.isTTY !== true || output.isTTY !== true) {
|
|
18
25
|
throw new Error("an interactive terminal is unavailable");
|
|
19
26
|
}
|
|
20
|
-
|
|
27
|
+
if (choices.length === 0)
|
|
28
|
+
throw new Error("project trust choices are unavailable");
|
|
29
|
+
const comparison = options.presentation === "comparison";
|
|
30
|
+
const renderBareDialog = comparison
|
|
21
31
|
? undefined : (await import("./project-trust-dialog.js")).renderProjectTrustDialog;
|
|
22
32
|
const wasRaw = input.isRaw === true;
|
|
23
33
|
let selected = 0;
|
|
@@ -27,19 +37,18 @@ export function createConsoleProjectTrustPrompt(options = {}) {
|
|
|
27
37
|
return;
|
|
28
38
|
restored = true;
|
|
29
39
|
input.setRawMode?.(wasRaw);
|
|
30
|
-
output.write(`${CLEAR_HOME}${
|
|
40
|
+
output.write(`${CLEAR_HOME}${EMERGENCY_TERMINAL_RESET}`);
|
|
31
41
|
};
|
|
32
42
|
const render = () => {
|
|
33
43
|
const width = Math.max(renderBareDialog ? 1 : 20, output.columns ?? 80);
|
|
34
44
|
const rows = Math.max(1, output.rows ?? 24);
|
|
35
|
-
const lines = (renderBareDialog?.(cwd, selected, width, rows) ?? [
|
|
45
|
+
const lines = (renderBareDialog?.(cwd, choices.map(choice => choice.label), selected, width, rows) ?? [
|
|
36
46
|
`${BOLD}${ACCENT}Trust project folder?${RESET_FG}${RESET_BOLD}`,
|
|
37
47
|
cwd,
|
|
38
48
|
"",
|
|
39
49
|
"This allows a1 to load project settings and resources, install missing project packages, and execute project extensions.",
|
|
40
50
|
"",
|
|
41
|
-
optionRow(
|
|
42
|
-
optionRow("Do not trust", selected === 1),
|
|
51
|
+
...choices.map((choice, index) => optionRow(choice.label, selected === index)),
|
|
43
52
|
"",
|
|
44
53
|
`${DIM}↑/↓${MUTED} to navigate ${DIM}Enter${MUTED} to select ${DIM}Esc${MUTED} to cancel${RESET_FG}`,
|
|
45
54
|
]).map(line => clipAnsiSafe(line, width));
|
|
@@ -66,19 +75,20 @@ export function createConsoleProjectTrustPrompt(options = {}) {
|
|
|
66
75
|
settled = true;
|
|
67
76
|
cleanup();
|
|
68
77
|
restore();
|
|
78
|
+
// Protocol: restoration preserves the parent cursor; only the shell paints there.
|
|
69
79
|
reject(error);
|
|
70
80
|
};
|
|
71
81
|
const onData = (chunk) => {
|
|
72
82
|
const data = chunk.toString();
|
|
73
83
|
for (let index = 0; index < data.length;) {
|
|
74
84
|
if (data.startsWith("\u001b[A", index)) {
|
|
75
|
-
selected =
|
|
85
|
+
selected = (selected - 1 + choices.length) % choices.length;
|
|
76
86
|
index += 3;
|
|
77
87
|
render();
|
|
78
88
|
continue;
|
|
79
89
|
}
|
|
80
90
|
if (data.startsWith("\u001b[B", index) || data[index] === "\t") {
|
|
81
|
-
selected = selected
|
|
91
|
+
selected = (selected + 1) % choices.length;
|
|
82
92
|
index += data[index] === "\t" ? 1 : 3;
|
|
83
93
|
render();
|
|
84
94
|
continue;
|
|
@@ -86,21 +96,31 @@ export function createConsoleProjectTrustPrompt(options = {}) {
|
|
|
86
96
|
const key = data[index] ?? "";
|
|
87
97
|
index += 1;
|
|
88
98
|
if (key === "\r" || key === "\n") {
|
|
89
|
-
finish(selected
|
|
99
|
+
finish(choices[selected]?.id ?? null);
|
|
90
100
|
return;
|
|
91
101
|
}
|
|
92
|
-
if (key === "\u001b"
|
|
93
|
-
|
|
102
|
+
if (key === "\u001b") {
|
|
103
|
+
if (comparison)
|
|
104
|
+
finish(null);
|
|
105
|
+
else
|
|
106
|
+
fail(new ProjectTrustPromptExitError());
|
|
94
107
|
return;
|
|
95
108
|
}
|
|
109
|
+
if (key === "\u0003") {
|
|
110
|
+
if (comparison) {
|
|
111
|
+
finish(null);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
96
116
|
// Compatibility: retain y/n aliases for terminals or automation that cannot send
|
|
97
117
|
// navigation keys; the visible interaction remains selector-first.
|
|
98
118
|
if (key === "y" || key === "Y") {
|
|
99
|
-
finish(
|
|
119
|
+
finish(choices.find(choice => choice.id === "trust")?.id ?? null);
|
|
100
120
|
return;
|
|
101
121
|
}
|
|
102
122
|
if (key === "n" || key === "N") {
|
|
103
|
-
finish(
|
|
123
|
+
finish(choices.find(choice => choice.id === "deny")?.id ?? null);
|
|
104
124
|
return;
|
|
105
125
|
}
|
|
106
126
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { writeSync } from "node:fs";
|
|
2
2
|
// Protocol: reset modes on both screens; setting keyboard flags to zero is idempotent,
|
|
3
3
|
// unlike repeatedly popping a keyboard-protocol stack owned by a parent application.
|
|
4
|
+
// Resetting parent-screen margins homes the cursor, so preserve its restored position.
|
|
4
5
|
export const EMERGENCY_TERMINAL_RESET = "\x1b[?2026l\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1004l\x1b[?1006l"
|
|
5
6
|
+ "\x1b[?2004l\x1b[=0u\x1b[>4;0m\x1b]8;;\x1b\\\x1b[0m\x1b[r\x1b[?7h\x1b[?25h"
|
|
6
|
-
+ "\x1b[?1049l\x1b[r\x1b[?7h\x1b[?25h\x1b]9;4;0\x07";
|
|
7
|
+
+ "\x1b[?1049l\x1b7\x1b[r\x1b8\x1b[?7h\x1b[?25h\x1b]9;4;0\x07";
|
|
7
8
|
export function restoreProcessTerminal() {
|
|
8
9
|
try {
|
|
9
10
|
if (process.stdout.isTTY)
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* Provenance: @earendil-works/pi-tui 0.87.1 (MIT), commit f07218c4d4bbc12bef056a7058c3dd49dfe41abe,
|
|
3
3
|
* packages/tui/src/components/editor.ts.
|
|
4
4
|
* Modifications: Owned editor core or minimal editor-local helper subset; public imports, strict
|
|
5
|
-
* types, typed persistent-history hooks,
|
|
6
|
-
* history label
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* types, typed persistent-history hooks, semantic border state with the user-approved numeric-only
|
|
6
|
+
* history label and separate centered history-overflow cue, and history-count retention during cursor
|
|
7
|
+
* placement within recalled multiline text. Public terminal runtime/exports remain shared and
|
|
8
|
+
* unchanged. See docs/architecture/history-editor-provenance.md.
|
|
9
|
+
* Deviations: compact-history-counter-label, history-overflow-cue-separation,
|
|
10
|
+
* history-recall-cursor-retention, persistent-history-owned-editor-boundary.
|
|
9
11
|
*/
|
|
10
12
|
import { type AutocompleteProvider, type Component, type Focusable, type TUI, type TuiMouseEvent, type TuiMouseEventResult, type SelectListTheme } from "@earendil-works/pi-tui";
|
|
11
13
|
import type { EditorInteractionPort, EditorRecallPort } from "../../editor-interaction.js";
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* Provenance: @earendil-works/pi-tui 0.87.1 (MIT), commit f07218c4d4bbc12bef056a7058c3dd49dfe41abe,
|
|
3
3
|
* packages/tui/src/components/editor.ts.
|
|
4
4
|
* Modifications: Owned editor core or minimal editor-local helper subset; public imports, strict
|
|
5
|
-
* types, typed persistent-history hooks,
|
|
6
|
-
* history label
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* types, typed persistent-history hooks, semantic border state with the user-approved numeric-only
|
|
6
|
+
* history label and separate centered history-overflow cue, and history-count retention during cursor
|
|
7
|
+
* placement within recalled multiline text. Public terminal runtime/exports remain shared and
|
|
8
|
+
* unchanged. See docs/architecture/history-editor-provenance.md.
|
|
9
|
+
* Deviations: compact-history-counter-label, history-overflow-cue-separation,
|
|
10
|
+
* history-recall-cursor-retention, persistent-history-owned-editor-boundary.
|
|
9
11
|
*/
|
|
10
12
|
import { getKeybindings, matchesKey, CURSOR_MARKER, sliceByColumn, truncateToWidth, visibleWidth, SelectList } from "@earendil-works/pi-tui";
|
|
11
13
|
import { decodePrintableKey } from "./printable-key.js";
|
|
@@ -187,21 +189,27 @@ function buildDebouncePattern(triggerCharacters) {
|
|
|
187
189
|
const escapedWithoutAt = triggerCharacters.filter((character) => character !== "@").map(escapeCharacterClass);
|
|
188
190
|
return new RegExp(`${autocompleteBoundaryRegex.source}(?:@(?:"[^"]*|${unquotedAutocompleteSuffixRegex.source})|[${escapedWithoutAt.join("")}]${unquotedAutocompleteSuffixRegex.source})$`, "u");
|
|
189
191
|
}
|
|
190
|
-
function createScrollBorder(direction, hiddenLineCount, width) {
|
|
192
|
+
function createScrollBorder(direction, hiddenLineCount, width, minimumCueStart = 0) {
|
|
191
193
|
const availableWidth = Math.max(0, width);
|
|
192
194
|
const label = ` ${direction} ${hiddenLineCount} more `;
|
|
193
195
|
const labelWidth = visibleWidth(label);
|
|
194
196
|
if (labelWidth + 2 <= availableWidth) {
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
+
const centeredStart = Math.floor((availableWidth - labelWidth) / 2);
|
|
198
|
+
const cueStart = Math.max(centeredStart, minimumCueStart);
|
|
199
|
+
if (cueStart + labelWidth <= availableWidth) {
|
|
200
|
+
return "─".repeat(cueStart) + label + "─".repeat(availableWidth - cueStart - labelWidth);
|
|
201
|
+
}
|
|
197
202
|
}
|
|
203
|
+
if (minimumCueStart > 0)
|
|
204
|
+
return "─".repeat(availableWidth);
|
|
198
205
|
const indicator = `─── ${direction} ${hiddenLineCount} more `;
|
|
199
|
-
const
|
|
206
|
+
const indicatorWidth = visibleWidth(indicator);
|
|
207
|
+
const remaining = availableWidth - indicatorWidth;
|
|
200
208
|
if (remaining >= 0)
|
|
201
209
|
return indicator + "─".repeat(remaining);
|
|
202
210
|
const ellipsis = "...".slice(0, availableWidth);
|
|
203
|
-
const
|
|
204
|
-
return sliceByColumn(indicator, 0,
|
|
211
|
+
const visibleIndicatorWidth = availableWidth - visibleWidth(ellipsis);
|
|
212
|
+
return sliceByColumn(indicator, 0, visibleIndicatorWidth, true) + ellipsis;
|
|
205
213
|
}
|
|
206
214
|
export class HistoryEditorCore {
|
|
207
215
|
segmentTransform;
|
|
@@ -479,8 +487,19 @@ export class HistoryEditorCore {
|
|
|
479
487
|
}
|
|
480
488
|
renderTopBorder(width, hiddenLineCount) {
|
|
481
489
|
if (this.persistentHistory && this.historyIndex >= 0) {
|
|
482
|
-
const
|
|
483
|
-
|
|
490
|
+
const history = `${this.history.length - this.historyIndex}/${this.history.length} `;
|
|
491
|
+
if (hiddenLineCount > 0) {
|
|
492
|
+
const historyStart = 4;
|
|
493
|
+
const historyEnd = historyStart + visibleWidth(history);
|
|
494
|
+
const historyAnchorStart = historyStart - 1;
|
|
495
|
+
if (historyEnd <= width) {
|
|
496
|
+
const border = createScrollBorder("↑", hiddenLineCount, width, historyEnd);
|
|
497
|
+
return this.borderColor(border.slice(0, historyAnchorStart) + " ")
|
|
498
|
+
+ this.styleHistoryLabel(history)
|
|
499
|
+
+ this.borderColor(border.slice(historyEnd));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const label = `─── ${history}`;
|
|
484
503
|
const shown = truncateToWidth(label, width);
|
|
485
504
|
const remaining = Math.max(0, width - visibleWidth(shown));
|
|
486
505
|
return this.borderColor(shown.slice(0, 4)) + this.styleHistoryLabel(shown.slice(4)) + this.borderColor("─".repeat(remaining));
|
|
@@ -642,7 +661,8 @@ export class HistoryEditorCore {
|
|
|
642
661
|
this.state.cursorLine = visualLine.logicalLine;
|
|
643
662
|
this.setCursorCol(visualLine.startCol + targetIndex);
|
|
644
663
|
this.lastAction = null;
|
|
645
|
-
|
|
664
|
+
// Cursor placement within recalled multiline text does not leave browsing:
|
|
665
|
+
// keep its position/total border visible until content changes or recall exits.
|
|
646
666
|
if (this.autocompleteState)
|
|
647
667
|
this.updateAutocomplete();
|
|
648
668
|
return { handled: true, focus: true };
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
export type PiProjectTrustChoiceId = "trust" | "trust-parent" | "trust-session" | "deny" | "deny-session";
|
|
1
2
|
export interface PiProjectTrustPreflightRequest {
|
|
2
3
|
readonly cwd: string;
|
|
3
4
|
readonly defaultDecision: "ask" | "always" | "never";
|
|
5
|
+
readonly choices: readonly {
|
|
6
|
+
readonly id: PiProjectTrustChoiceId;
|
|
7
|
+
readonly label: string;
|
|
8
|
+
}[];
|
|
4
9
|
}
|
|
5
|
-
export type PiProjectTrustPreflightPrompt = (request: PiProjectTrustPreflightRequest) => Promise<
|
|
10
|
+
export type PiProjectTrustPreflightPrompt = (request: PiProjectTrustPreflightRequest) => Promise<PiProjectTrustChoiceId | null>;
|
|
6
11
|
export interface PiProjectTrustPreflightResult {
|
|
7
12
|
readonly trusted: boolean;
|
|
8
13
|
readonly source: "saved" | "default" | "interactive" | "fail-closed";
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
1
3
|
import { ProjectTrustStore, SettingsManager, } from "../startup-public.js";
|
|
2
4
|
/**
|
|
3
5
|
* Resolves trust from global state only. It never constructs a project-trusted
|
|
@@ -25,7 +27,12 @@ export async function resolvePiProjectTrustPreflight(options) {
|
|
|
25
27
|
};
|
|
26
28
|
}
|
|
27
29
|
try {
|
|
28
|
-
const
|
|
30
|
+
const choices = projectTrustChoices(options.cwd);
|
|
31
|
+
const decision = await options.prompt({
|
|
32
|
+
cwd: options.cwd,
|
|
33
|
+
defaultDecision: fallback,
|
|
34
|
+
choices: choices.map(({ id, label }) => ({ id, label })),
|
|
35
|
+
});
|
|
29
36
|
if (decision === null) {
|
|
30
37
|
return {
|
|
31
38
|
trusted: false,
|
|
@@ -33,10 +40,18 @@ export async function resolvePiProjectTrustPreflight(options) {
|
|
|
33
40
|
diagnostic: `Project resources in ${options.cwd} were withheld because trust selection was cancelled`,
|
|
34
41
|
};
|
|
35
42
|
}
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
const selected = choices.find(choice => choice.id === decision);
|
|
44
|
+
if (selected === undefined)
|
|
45
|
+
throw new Error(`unknown trust selection: ${decision}`);
|
|
46
|
+
if (selected.updates.length > 0)
|
|
47
|
+
trustStore.setMany([...selected.updates]);
|
|
48
|
+
return { trusted: selected.trusted, source: "interactive", diagnostic: null };
|
|
38
49
|
}
|
|
39
50
|
catch (error) {
|
|
51
|
+
// Protocol: an explicit bare-A1 exit propagates after the prompt restores the terminal;
|
|
52
|
+
// ordinary prompt failures still continue through the fail-closed restricted shell.
|
|
53
|
+
if (error instanceof Error && error.name === "ProjectTrustPromptExitError")
|
|
54
|
+
throw error;
|
|
40
55
|
return {
|
|
41
56
|
trusted: false,
|
|
42
57
|
source: "fail-closed",
|
|
@@ -44,3 +59,26 @@ export async function resolvePiProjectTrustPreflight(options) {
|
|
|
44
59
|
};
|
|
45
60
|
}
|
|
46
61
|
}
|
|
62
|
+
/** Pinned Pi's five startup outcomes, with canonical parent scope resolved before project resources load. */
|
|
63
|
+
function projectTrustChoices(cwd) {
|
|
64
|
+
let trustPath = resolve(cwd);
|
|
65
|
+
try {
|
|
66
|
+
trustPath = realpathSync(trustPath);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Compatibility: pinned Pi keeps the resolved path when real-path lookup fails.
|
|
70
|
+
}
|
|
71
|
+
const parent = dirname(trustPath);
|
|
72
|
+
return [
|
|
73
|
+
{ id: "trust", label: "Trust", trusted: true, updates: [{ path: trustPath, decision: true }] },
|
|
74
|
+
...(parent === trustPath ? [] : [{
|
|
75
|
+
id: "trust-parent",
|
|
76
|
+
label: `Trust parent folder (${parent})`,
|
|
77
|
+
trusted: true,
|
|
78
|
+
updates: [{ path: parent, decision: true }, { path: trustPath, decision: null }],
|
|
79
|
+
}]),
|
|
80
|
+
{ id: "trust-session", label: "Trust (this session only)", trusted: true, updates: [] },
|
|
81
|
+
{ id: "deny", label: "Do not trust", trusted: false, updates: [{ path: trustPath, decision: false }] },
|
|
82
|
+
{ id: "deny-session", label: "Do not trust (this session only)", trusted: false, updates: [] },
|
|
83
|
+
];
|
|
84
|
+
}
|
|
@@ -91,7 +91,7 @@ export async function createPiRuntimeIntegration(options) {
|
|
|
91
91
|
...created,
|
|
92
92
|
services,
|
|
93
93
|
diagnostics: [
|
|
94
|
-
...(trust.diagnostic === null ? [] : [{ type: "warning", message: trust.diagnostic }]),
|
|
94
|
+
...(trust.diagnostic === null ? [] : [{ type: "warning", code: "project-trust", message: trust.diagnostic }]),
|
|
95
95
|
...modelScope.diagnostics,
|
|
96
96
|
],
|
|
97
97
|
};
|
|
@@ -116,7 +116,10 @@ export class PiEngineRuntime {
|
|
|
116
116
|
this.#ports.rebound();
|
|
117
117
|
});
|
|
118
118
|
for (const diagnostic of [...runtime.diagnostics, ...runtime.services.diagnostics]) {
|
|
119
|
-
|
|
119
|
+
const code = "code" in diagnostic && diagnostic.code === "project-trust"
|
|
120
|
+
? "project-trust"
|
|
121
|
+
: "engine-startup";
|
|
122
|
+
this.#ports.diagnostic(diagnostic.type === "error" ? "error" : diagnostic.type === "warning" ? "warning" : "info", code, diagnostic.message, diagnostic.type !== "error");
|
|
120
123
|
}
|
|
121
124
|
this.bindSession(runtime.session);
|
|
122
125
|
await this.#announceChangelog(runtime.services.settingsManager);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "darwin",
|
|
6
6
|
"architecture": "arm64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-28T08:09:05.323Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "9db1726bbe3fc2e8292f2ead1e7e9d0fd4d7d0dc9217b372582bf354b0f565dc",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "linux",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-28T08:08:49.459Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "d8cda6b0c7cb36c0cc41e90802aceebf6c02eaebbc08a83d7d963d2e918dbd7a",
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"platform": "win32",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-28T08:09:42.804Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "9f36428f29224cc424dd262aa0495a075e915c9ecce6bdfcc45bd972270afdde",
|
|
12
12
|
"size": 178176
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|
|
@@ -41,11 +41,19 @@ The editor renders position/overflow in its existing border. The position label
|
|
|
41
41
|
uses an injected neutral status-text style (`dim`), while the surrounding rules
|
|
42
42
|
retain the active input-border color, as clarified during manual review. The approved
|
|
43
43
|
`show-autocomplete-above-prompt` review refinement omits the literal `History` title:
|
|
44
|
-
`─── 1/100 ─…` keeps the same four-cell inset, count calculation, dim color,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
`─── 1/100 ─…` keeps the same four-cell inset, count calculation, dim color, and
|
|
45
|
+
clipping. When recalled input has hidden lines above, the approved
|
|
46
|
+
`separate-history-scroll-label` refinement keeps that count left-aligned without a
|
|
47
|
+
separator and independently centers `↑ N more` in the active border color, matching
|
|
48
|
+
the lower cue. At collision widths the history count remains visible while a complete
|
|
49
|
+
cue shifts right only as needed, or is omitted when both labels cannot fit. Cursor
|
|
50
|
+
movement and pointer placement within recalled multiline text retain the active browse
|
|
51
|
+
position and its count; content mutation or leaving recall still ends that presentation.
|
|
52
|
+
Core and shell regressions verify numbering, 100-entry recall, separated styling and
|
|
53
|
+
placement, multiline cursor-placement continuity, narrow-width history continuity and
|
|
54
|
+
recovery, draft restoration, and the absence of the title. Recall temporarily separates the draft's
|
|
55
|
+
live paste backing from recalled literal text, restoring it on return; ordinary
|
|
56
|
+
pinned mode remains source-equivalent.
|
|
49
57
|
|
|
50
58
|
The integration inventory is:
|
|
51
59
|
|
package/package.json
CHANGED